diff --git a/DEPS b/DEPS
index 904c01a..08818dfd 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,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': '5c4c61eeb38fbad721e42ec68f486514a2450418',
+  'skia_revision': 'c2d325c1d67a1086a27145c33ba6f20e897e2d06',
   # 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': '754d46b1a05b1da93cce3af187a001a11e5b193c',
+  'v8_revision': '3714932e224a7a48553563a7eb18e2ffc000d796',
   # 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.
@@ -187,7 +187,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'c77cb596a2c3bf94822d5d9528ad964fef9ac35f',
+  'angle_revision': 'b69c4e14c5e292d6c966b14b41ae7064d753c501',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -238,7 +238,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '54f9ec739967056587fe7c3f2dcf1e6939054403',
+  'catapult_revision': '8351d2182b54cb47484d7228db3240a0d44bb550',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -246,7 +246,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '449349092a72debbb77daab9a1c8338cf2f8e6d4',
+  'devtools_frontend_revision': 'd5a3755f19aa248cffbcf61f1628362aaa6c0377',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -302,7 +302,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '046389926f109f705803d7dd13223f0868fff067',
+  'dawn_revision': 'd5db214564bd781d5a6438a5fa48e39c9ba021b5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -877,7 +877,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e2ac022f0bdfbb36ddf355eb4da0151081b02130',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'de6c4564661837f50ca5aaa55c18b45ce4a9d97b',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
diff --git a/android_webview/browser/aw_contents.h b/android_webview/browser/aw_contents.h
index 035eb6e..f4dcc80 100644
--- a/android_webview/browser/aw_contents.h
+++ b/android_webview/browser/aw_contents.h
@@ -322,6 +322,8 @@
                   jboolean include_disk_files);
   void KillRenderProcess(JNIEnv* env,
                          const base::android::JavaParamRef<jobject>& obj);
+  // See //android_webview/docs/how-does-on-create-window-work.md for more
+  // details.
   void SetPendingWebContentsForPopup(
       std::unique_ptr<content::WebContents> pending);
   jlong ReleasePopupAwContents(JNIEnv* env,
@@ -413,6 +415,8 @@
   std::unique_ptr<AwRenderViewHostExt> render_view_host_ext_;
   std::unique_ptr<FindHelper> find_helper_;
   std::unique_ptr<IconHelper> icon_helper_;
+  // See //android_webview/docs/how-does-on-create-window-work.md for more
+  // details for |pending_contents_|.
   std::unique_ptr<AwContents> pending_contents_;
   std::unique_ptr<AwPdfExporter> pdf_exporter_;
   std::unique_ptr<PermissionRequestHandler> permission_request_handler_;
diff --git a/android_webview/browser/aw_web_contents_delegate.h b/android_webview/browser/aw_web_contents_delegate.h
index 6cfb803..0ad4b015 100644
--- a/android_webview/browser/aw_web_contents_delegate.h
+++ b/android_webview/browser/aw_web_contents_delegate.h
@@ -41,6 +41,8 @@
   void RunFileChooser(content::RenderFrameHost* render_frame_host,
                       std::unique_ptr<content::FileSelectListener> listener,
                       const blink::mojom::FileChooserParams& params) override;
+  // See //android_webview/docs/how-does-on-create-window-work.md for more
+  // details.
   void AddNewContents(content::WebContents* source,
                       std::unique_ptr<content::WebContents> new_contents,
                       WindowOpenDisposition disposition,
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
index 157688d..ca6aacb 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
@@ -265,6 +265,7 @@
         }
     }
 
+    // See //android_webview/docs/how-does-on-create-window-work.md for more details.
     static void completeWindowCreation(WebView parent, WebView child) {
         AwContents parentContents = ((WebViewChromium) parent.getWebViewProvider()).mAwContents;
         AwContents childContents =
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
index 61a155a..ca006c6a 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
@@ -111,6 +111,7 @@
         super(webView, webViewDelegate, context);
         try (ScopedSysTraceEvent event =
                         ScopedSysTraceEvent.scoped("WebViewContentsClientAdapter.constructor")) {
+            // See //android_webview/docs/how-does-on-create-window-work.md for more details.
             mUiThreadHandler = new Handler() {
                 @Override
                 public void handleMessage(Message msg) {
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 1c573279..d56d610 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -1296,6 +1296,8 @@
     /**
      * Called on the "source" AwContents that is opening the popup window to
      * provide the AwContents to host the pop up content.
+     *
+     * See //android_webview/docs/how-does-on-create-window-work.md for more details.
      */
     public void supplyContentsForPopup(AwContents newContents) {
         if (isDestroyed(WARN)) return;
@@ -1316,6 +1318,7 @@
 
     // Recap: supplyContentsForPopup() is called on the parent window's content, this method is
     // called on the popup window's content.
+    // See //android_webview/docs/how-does-on-create-window-work.md for more details.
     private void receivePopupContents(long popupNativeAwContents) {
         if (isDestroyed(WARN)) return;
         // Save existing view state.
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java
index 9e4f52fa..b5f1404 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java
@@ -25,6 +25,7 @@
     public abstract void runFileChooser(int processId, int renderId, int modeFlags,
             String acceptTypes, String title, String defaultFilename,  boolean capture);
 
+    // See //android_webview/docs/how-does-on-create-window-work.md for more details.
     @CalledByNative
     public abstract boolean addNewContents(boolean isDialog, boolean isUserGesture);
 
diff --git a/ash/app_list/app_list_presenter_impl_unittest.cc b/ash/app_list/app_list_presenter_impl_unittest.cc
index 238b32a..5b1c03b7 100644
--- a/ash/app_list/app_list_presenter_impl_unittest.cc
+++ b/ash/app_list/app_list_presenter_impl_unittest.cc
@@ -22,7 +22,6 @@
 #include "ui/aura/window.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/views/test/test_views_delegate.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/window_util.h"
 
 namespace ash {
@@ -112,7 +111,6 @@
 
 void AppListPresenterImplTest::SetUp() {
   AuraTestBase::SetUp();
-  new wm::DefaultActivationClient(root_window());
   container_.reset(CreateNormalWindow(kShellWindowId_AppListContainer,
                                       root_window(), nullptr));
   std::unique_ptr<AppListPresenterDelegateTest> presenter_delegate =
diff --git a/ash/display/display_prefs_unittest.cc b/ash/display/display_prefs_unittest.cc
index bc94956b..c4aac0c 100644
--- a/ash/display/display_prefs_unittest.cc
+++ b/ash/display/display_prefs_unittest.cc
@@ -50,6 +50,8 @@
 namespace ash {
 
 namespace {
+const char kPrimaryIdKey[] = "primary-id";
+
 bool IsRotationLocked() {
   return ash::Shell::Get()->screen_orientation_controller()->rotation_locked();
 }
@@ -505,14 +507,6 @@
   EXPECT_EQ(id2, stored_placement.parent_display_id);
   EXPECT_EQ(id2, stored_layout.primary_id);
 
-  // TODO(oshima): Make the below pass and re-enable, https://crbug.com/1063529
-#if 0
-  const char kPrimaryIdKey[] = "primary-id";
-  const char kPositionKey[] = "position";
-  const char kOffsetKey[] = "offset";
-  const char kPlacementDisplayIdKey[] = "placement.display_id";
-  const char kPlacementParentDisplayIdKey[] = "placement.parent_display_id";
-
   std::string primary_id_str;
   EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
   EXPECT_EQ(base::NumberToString(id2), primary_id_str);
@@ -521,24 +515,17 @@
       display::test::CreateDisplayLayout(Shell::Get()->display_manager(),
                                          display::DisplayPlacement::BOTTOM,
                                          20));
-
+  // Test Hardware Mirroring scenario.
   UpdateDisplay("1+0-200x200*2,1+0-200x200");
-  // Mirrored.
-  int offset = 0;
-  std::string position;
-  EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
-  EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
-  EXPECT_EQ("bottom", position);
-  EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
-  EXPECT_EQ(20, offset);
-  std::string id;
-  EXPECT_TRUE(layout_value->GetString(kPlacementDisplayIdKey, &id));
-  EXPECT_EQ(base::NumberToString(id1), id);
-  EXPECT_TRUE(layout_value->GetString(kPlacementParentDisplayIdKey, &id));
-  EXPECT_EQ(base::NumberToString(id2), id);
+  EXPECT_FALSE(display_manager()->IsInSoftwareMirrorMode());
+  EXPECT_TRUE(display_manager()->IsInHardwareMirrorMode());
 
-  EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
-  EXPECT_EQ(base::NumberToString(id2), primary_id_str);
+  EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
+  EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout));
+  EXPECT_EQ(display::DisplayPlacement::BOTTOM, stored_placement.position);
+  EXPECT_EQ(20, stored_placement.offset);
+  EXPECT_EQ(id1, stored_placement.display_id);
+  EXPECT_EQ(id2, stored_placement.parent_display_id);
 
   EXPECT_TRUE(properties->GetDictionary(base::NumberToString(id1), &property));
   EXPECT_FALSE(property->GetInteger("width", &width));
@@ -547,7 +534,8 @@
   external_display_mirror_info =
       local_state()->GetList(prefs::kExternalDisplayMirrorInfo);
   EXPECT_EQ(1U, external_display_mirror_info->GetSize());
-  EXPECT_EQ(base::NumberToString(id2),
+  // ExternalDisplayInfo stores ID without output index.
+  EXPECT_EQ(base::NumberToString(display::GetDisplayIdWithoutOutputIndex(id2)),
             external_display_mirror_info->GetList()[0].GetString());
 
   // External display's selected resolution must not change
@@ -564,15 +552,18 @@
       1.0f, 60.f, false);
 
   UpdateDisplay("200x200*2, 600x500#600x500|500x400");
+  EXPECT_FALSE(display_manager()->IsInMirrorMode());
 
   // Update key as the 2nd display gets new id.
   id2 = display_manager_test.GetSecondaryDisplay().id();
   key = base::NumberToString(id1) + "," + base::NumberToString(id2);
+
   EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
-  EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
-  EXPECT_EQ("right", position);
-  EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
-  EXPECT_EQ(0, offset);
+  EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout));
+  EXPECT_EQ(display::DisplayPlacement::RIGHT, stored_placement.position);
+  EXPECT_EQ(0, stored_placement.offset);
+  EXPECT_EQ(id1, stored_placement.parent_display_id);
+  EXPECT_EQ(id2, stored_placement.display_id);
   EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
   EXPECT_EQ(base::NumberToString(id1), primary_id_str);
 
@@ -588,14 +579,14 @@
   // Disconnect 2nd display first to generate new id for external display.
   UpdateDisplay("200x200*2");
   UpdateDisplay("200x200*2, 500x400#600x500|500x400%60.0f");
+
   // Update key as the 2nd display gets new id.
   id2 = display_manager_test.GetSecondaryDisplay().id();
   key = base::NumberToString(id1) + "," + base::NumberToString(id2);
   EXPECT_TRUE(displays->GetDictionary(key, &layout_value));
-  EXPECT_TRUE(layout_value->GetString(kPositionKey, &position));
-  EXPECT_EQ("right", position);
-  EXPECT_TRUE(layout_value->GetInteger(kOffsetKey, &offset));
-  EXPECT_EQ(0, offset);
+  EXPECT_TRUE(display::JsonToDisplayLayout(*layout_value, &stored_layout));
+  EXPECT_EQ(display::DisplayPlacement::RIGHT, stored_placement.position);
+  EXPECT_EQ(0, stored_placement.offset);
   EXPECT_TRUE(layout_value->GetString(kPrimaryIdKey, &primary_id_str));
   EXPECT_EQ(base::NumberToString(id1), primary_id_str);
 
@@ -605,7 +596,6 @@
   EXPECT_TRUE(property->GetInteger("height", &height));
   EXPECT_EQ(500, width);
   EXPECT_EQ(400, height);
-#endif
 }
 
 TEST_F(DisplayPrefsTest, PreventStore) {
diff --git a/ash/keyboard/ui/keyboard_ui_controller_unittest.cc b/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
index ffe16fe..7506021 100644
--- a/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
+++ b/ash/keyboard/ui/keyboard_ui_controller_unittest.cc
@@ -38,7 +38,6 @@
 #include "ui/compositor/test/layer_animator_test_controller.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/default_screen_position_client.h"
 
 #if defined(USE_OZONE)
@@ -147,7 +146,6 @@
 
   void SetUp() override {
     aura::test::AuraTestBase::SetUp();
-    new wm::DefaultActivationClient(root_window());
     focus_controller_ = std::make_unique<TestFocusController>(root_window());
     layout_delegate_ =
         std::make_unique<TestKeyboardLayoutDelegate>(root_window());
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 2a82d8b..7f7853c8 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -205,6 +205,7 @@
     "shelf_prefs.h",
     "shelf_types.cc",
     "shelf_types.h",
+    "shelf_ui_info.cc",
     "shelf_ui_info.h",
     "shell_window_ids.cc",
     "shell_window_ids.h",
diff --git a/ash/public/cpp/shelf_ui_info.cc b/ash/public/cpp/shelf_ui_info.cc
new file mode 100644
index 0000000..afba7c7
--- /dev/null
+++ b/ash/public/cpp/shelf_ui_info.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/shelf_ui_info.h"
+
+namespace ash {
+
+ScrollableShelfInfo::ScrollableShelfInfo() = default;
+
+ScrollableShelfInfo::ScrollableShelfInfo(const ScrollableShelfInfo& rhs) =
+    default;
+
+ScrollableShelfInfo& ScrollableShelfInfo::operator=(
+    const ScrollableShelfInfo& rhs) = default;
+
+ScrollableShelfInfo::~ScrollableShelfInfo() = default;
+
+}  // namespace ash
diff --git a/ash/public/cpp/shelf_ui_info.h b/ash/public/cpp/shelf_ui_info.h
index fe9a8337..6f4cb49 100644
--- a/ash/public/cpp/shelf_ui_info.h
+++ b/ash/public/cpp/shelf_ui_info.h
@@ -13,6 +13,11 @@
 namespace ash {
 
 struct ASH_PUBLIC_EXPORT ScrollableShelfInfo {
+  ScrollableShelfInfo();
+  ScrollableShelfInfo(const ScrollableShelfInfo& info);
+  ScrollableShelfInfo& operator=(const ScrollableShelfInfo& info);
+  ~ScrollableShelfInfo();
+
   // Current offset on the main axis.
   float main_axis_offset = 0.f;
 
@@ -33,6 +38,9 @@
 
   // Indicates whether scrollable shelf is in overflow mode.
   bool is_overflow = false;
+
+  // Screen bounds of visible shelf icons.
+  std::vector<gfx::Rect> icons_bounds_in_screen;
 };
 
 struct ASH_PUBLIC_EXPORT ShelfState {
diff --git a/ash/shelf/shelf_test_api.cc b/ash/shelf/shelf_test_api.cc
index f88e958..e781530 100644
--- a/ash/shelf/shelf_test_api.cc
+++ b/ash/shelf/shelf_test_api.cc
@@ -77,6 +77,13 @@
   info.is_overflow = (scrollable_shelf_view->layout_strategy_ !=
                       ScrollableShelfView::kNotShowArrowButtons);
 
+  const ShelfView* const shelf_view = scrollable_shelf_view->shelf_view_;
+  for (int i = shelf_view->first_visible_index();
+       i <= shelf_view->last_visible_index(); ++i) {
+    info.icons_bounds_in_screen.push_back(
+        shelf_view->view_model()->view_at(i)->GetBoundsInScreen());
+  }
+
   // Calculates the target offset only when |scroll_distance| is specified.
   if (state.scroll_distance != 0.f) {
     const float target_offset =
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index f3647a7..2b3d17d 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -267,6 +267,7 @@
   }
   ShelfWidget* shelf_widget() const { return shelf_->shelf_widget(); }
   views::ViewModel* view_model() { return view_model_.get(); }
+  const views::ViewModel* view_model() const { return view_model_.get(); }
   bool dragged_off_shelf() const { return dragged_off_shelf_; }
   ShelfID drag_and_drop_shelf_id() const { return drag_and_drop_shelf_id_; }
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index e34c692..87aead9 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -725,6 +725,8 @@
     "threading/post_task_and_reply_impl.h",
     "threading/scoped_blocking_call.cc",
     "threading/scoped_blocking_call.h",
+    "threading/scoped_blocking_call_internal.cc",
+    "threading/scoped_blocking_call_internal.h",
     "threading/scoped_thread_priority.cc",
     "threading/scoped_thread_priority.h",
     "threading/sequence_bound.h",
diff --git a/base/task/thread_pool/task_tracker.cc b/base/task/thread_pool/task_tracker.cc
index e4ecc57..bccabc4 100644
--- a/base/task/thread_pool/task_tracker.cc
+++ b/base/task/thread_pool/task_tracker.cc
@@ -487,16 +487,15 @@
     RegisteredTaskSource task_source) {
   DCHECK(task_source);
 
-  const bool task_is_worker_task =
-      BeforeRunTask(task_source->shutdown_behavior());
+  const bool should_run_tasks = BeforeRunTask(task_source->shutdown_behavior());
 
   // Run the next task in |task_source|.
   Optional<Task> task;
   TaskTraits traits;
   {
     auto transaction = task_source->BeginTransaction();
-    task = task_is_worker_task ? task_source.TakeTask(&transaction)
-                               : task_source.Clear(&transaction);
+    task = should_run_tasks ? task_source.TakeTask(&transaction)
+                            : task_source.Clear(&transaction);
     traits = transaction.traits();
   }
 
@@ -504,7 +503,7 @@
     // Run the |task| (whether it's a worker task or the Clear() closure).
     RunTask(std::move(task.value()), task_source.get(), traits);
   }
-  if (task_is_worker_task)
+  if (should_run_tasks)
     AfterRunTask(task_source->shutdown_behavior());
   const bool task_source_must_be_queued = task_source.DidProcessTask();
   // |task_source| should be reenqueued iff requested by DidProcessTask().
diff --git a/base/task/thread_pool/test_utils.cc b/base/task/thread_pool/test_utils.cc
index ba7c97f3..3b58e9f 100644
--- a/base/task/thread_pool/test_utils.cc
+++ b/base/task/thread_pool/test_utils.cc
@@ -11,7 +11,7 @@
 #include "base/task/thread_pool/pooled_parallel_task_runner.h"
 #include "base/task/thread_pool/pooled_sequenced_task_runner.h"
 #include "base/test/bind_test_util.h"
-#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "base/threading/thread_restrictions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/base/task/thread_pool/thread_group_impl.cc b/base/task/thread_pool/thread_group_impl.cc
index e896b2a..bbd7b2b 100644
--- a/base/task/thread_pool/thread_group_impl.cc
+++ b/base/task/thread_pool/thread_group_impl.cc
@@ -30,6 +30,7 @@
 #include "base/task/thread_pool/task_tracker.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time_override.h"
diff --git a/base/task/thread_pool/thread_group_native_win.cc b/base/task/thread_pool/thread_group_native_win.cc
index 4b8c5d2..d6adc707b 100644
--- a/base/task/thread_pool/thread_group_native_win.cc
+++ b/base/task/thread_pool/thread_group_native_win.cc
@@ -6,7 +6,7 @@
 
 #include "base/optional.h"
 #include "base/task/thread_pool/task_tracker.h"
-#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "base/win/scoped_com_initializer.h"
 
 namespace base {
diff --git a/base/task/thread_pool/thread_group_unittest.cc b/base/task/thread_pool/thread_group_unittest.cc
index e13de1b..d6099cc 100644
--- a/base/task/thread_pool/thread_group_unittest.cc
+++ b/base/task/thread_pool/thread_group_unittest.cc
@@ -24,6 +24,7 @@
 #include "base/test/test_timeouts.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "base/threading/simple_thread.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_restrictions.h"
diff --git a/base/threading/scoped_blocking_call.cc b/base/threading/scoped_blocking_call.cc
index 54bc1133..2b501b77b 100644
--- a/base/threading/scoped_blocking_call.cc
+++ b/base/threading/scoped_blocking_call.cc
@@ -5,7 +5,6 @@
 #include "base/threading/scoped_blocking_call.h"
 
 #include "base/lazy_instance.h"
-#include "base/scoped_clear_last_error.h"
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
@@ -16,17 +15,6 @@
 
 namespace {
 
-// The first 8 characters of sha1 of "ScopedBlockingCall".
-// echo -n "ScopedBlockingCall" | sha1sum
-constexpr uint32_t kActivityTrackerId = 0x11be9915;
-
-LazyInstance<ThreadLocalPointer<internal::BlockingObserver>>::Leaky
-    tls_blocking_observer = LAZY_INSTANCE_INITIALIZER;
-
-// Last ScopedBlockingCall instantiated on this thread.
-LazyInstance<ThreadLocalPointer<internal::UncheckedScopedBlockingCall>>::Leaky
-    tls_last_scoped_blocking_call = LAZY_INSTANCE_INITIALIZER;
-
 #if DCHECK_IS_ON()
 // Used to verify that the trace events used in the constructor do not result in
 // instantiating a ScopedBlockingCall themselves (which would cause an infinite
@@ -37,49 +25,6 @@
 
 }  // namespace
 
-namespace internal {
-
-UncheckedScopedBlockingCall::UncheckedScopedBlockingCall(
-    const Location& from_here,
-    BlockingType blocking_type)
-    : blocking_observer_(tls_blocking_observer.Get().Get()),
-      previous_scoped_blocking_call_(tls_last_scoped_blocking_call.Get().Get()),
-      is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
-                     (previous_scoped_blocking_call_ &&
-                      previous_scoped_blocking_call_->is_will_block_)),
-      scoped_activity_(from_here, 0, kActivityTrackerId, 0) {
-  tls_last_scoped_blocking_call.Get().Set(this);
-
-  if (blocking_observer_) {
-    if (!previous_scoped_blocking_call_) {
-      blocking_observer_->BlockingStarted(blocking_type);
-    } else if (blocking_type == BlockingType::WILL_BLOCK &&
-               !previous_scoped_blocking_call_->is_will_block_) {
-      blocking_observer_->BlockingTypeUpgraded();
-    }
-  }
-
-  if (scoped_activity_.IsRecorded()) {
-    // Also record the data for extended crash reporting.
-    const base::TimeTicks now = base::TimeTicks::Now();
-    auto& user_data = scoped_activity_.user_data();
-    user_data.SetUint("timestamp_us", now.since_origin().InMicroseconds());
-    user_data.SetUint("blocking_type", static_cast<uint64_t>(blocking_type));
-  }
-}
-
-UncheckedScopedBlockingCall::~UncheckedScopedBlockingCall() {
-  // TLS affects result of GetLastError() on Windows. ScopedClearLastError
-  // prevents side effect.
-  base::internal::ScopedClearLastError save_last_error;
-  DCHECK_EQ(this, tls_last_scoped_blocking_call.Get().Get());
-  tls_last_scoped_blocking_call.Get().Set(previous_scoped_blocking_call_);
-  if (blocking_observer_ && !previous_scoped_blocking_call_)
-    blocking_observer_->BlockingEnded();
-}
-
-}  // namespace internal
-
 ScopedBlockingCall::ScopedBlockingCall(const Location& from_here,
                                        BlockingType blocking_type)
     : UncheckedScopedBlockingCall(from_here, blocking_type) {
@@ -127,26 +72,6 @@
   TRACE_EVENT_END0("base", "ScopedBlockingCallWithBaseSyncPrimitives");
 }
 
-void SetBlockingObserverForCurrentThread(BlockingObserver* blocking_observer) {
-  DCHECK(!tls_blocking_observer.Get().Get());
-  tls_blocking_observer.Get().Set(blocking_observer);
-}
-
-void ClearBlockingObserverForCurrentThread() {
-  tls_blocking_observer.Get().Set(nullptr);
-}
-
-ScopedClearBlockingObserverForTesting::ScopedClearBlockingObserverForTesting()
-    : blocking_observer_(tls_blocking_observer.Get().Get()) {
-  tls_blocking_observer.Get().Set(nullptr);
-}
-
-ScopedClearBlockingObserverForTesting::
-    ~ScopedClearBlockingObserverForTesting() {
-  DCHECK(!tls_blocking_observer.Get().Get());
-  tls_blocking_observer.Get().Set(blocking_observer_);
-}
-
 }  // namespace internal
 
 }  // namespace base
diff --git a/base/threading/scoped_blocking_call.h b/base/threading/scoped_blocking_call.h
index 948434f..1681fe9 100644
--- a/base/threading/scoped_blocking_call.h
+++ b/base/threading/scoped_blocking_call.h
@@ -6,9 +6,8 @@
 #define BASE_THREADING_SCOPED_BLOCKING_CALL_H
 
 #include "base/base_export.h"
-#include "base/debug/activity_tracker.h"
 #include "base/location.h"
-#include "base/logging.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 
 namespace base {
 
@@ -28,35 +27,6 @@
   WILL_BLOCK
 };
 
-namespace internal {
-
-class BlockingObserver;
-
-// Common implementation class for both ScopedBlockingCall and
-// ScopedBlockingCallWithBaseSyncPrimitives without assertions.
-class BASE_EXPORT UncheckedScopedBlockingCall {
- public:
-  explicit UncheckedScopedBlockingCall(const Location& from_here,
-                                       BlockingType blocking_type);
-  ~UncheckedScopedBlockingCall();
-
- private:
-  internal::BlockingObserver* const blocking_observer_;
-
-  // Previous ScopedBlockingCall instantiated on this thread.
-  UncheckedScopedBlockingCall* const previous_scoped_blocking_call_;
-
-  // Whether the BlockingType of the current thread was WILL_BLOCK after this
-  // ScopedBlockingCall was instantiated.
-  const bool is_will_block_;
-
-  base::debug::ScopedActivity scoped_activity_;
-
-  DISALLOW_COPY_AND_ASSIGN(UncheckedScopedBlockingCall);
-};
-
-}  // namespace internal
-
 // This class must be instantiated in every scope where a blocking call is made
 // and serves as a precise annotation of the scope that may/will block for the
 // scheduler. When a ScopedBlockingCall is instantiated, it asserts that
@@ -123,6 +93,7 @@
   ~ScopedBlockingCall();
 };
 
+// Usage reserved for //base callers.
 namespace internal {
 
 // This class must be instantiated in every scope where a sync primitive is
@@ -138,47 +109,6 @@
   ~ScopedBlockingCallWithBaseSyncPrimitives();
 };
 
-// Interface for an observer to be informed when a thread enters or exits
-// the scope of ScopedBlockingCall objects.
-class BASE_EXPORT BlockingObserver {
- public:
-  virtual ~BlockingObserver() = default;
-
-  // Invoked when a ScopedBlockingCall is instantiated on the observed thread
-  // where there wasn't an existing ScopedBlockingCall.
-  virtual void BlockingStarted(BlockingType blocking_type) = 0;
-
-  // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the
-  // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a
-  // WILL_BLOCK ScopedBlockingCall.
-  virtual void BlockingTypeUpgraded() = 0;
-
-  // Invoked when the last ScopedBlockingCall on the observed thread is
-  // destroyed.
-  virtual void BlockingEnded() = 0;
-};
-
-// Registers |blocking_observer| on the current thread. It is invalid to call
-// this on a thread where there is an active ScopedBlockingCall.
-BASE_EXPORT void SetBlockingObserverForCurrentThread(
-    BlockingObserver* blocking_observer);
-
-BASE_EXPORT void ClearBlockingObserverForCurrentThread();
-
-// Unregisters the |blocking_observer| on the current thread within its scope.
-// Used in ThreadPool tests to prevent calls to //base sync primitives from
-// affecting the thread pool capacity.
-class BASE_EXPORT ScopedClearBlockingObserverForTesting {
- public:
-  ScopedClearBlockingObserverForTesting();
-  ~ScopedClearBlockingObserverForTesting();
-
- private:
-  BlockingObserver* const blocking_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedClearBlockingObserverForTesting);
-};
-
 }  // namespace internal
 
 }  // namespace base
diff --git a/base/threading/scoped_blocking_call_internal.cc b/base/threading/scoped_blocking_call_internal.cc
new file mode 100644
index 0000000..5d23172a
--- /dev/null
+++ b/base/threading/scoped_blocking_call_internal.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/scoped_blocking_call_internal.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/scoped_clear_last_error.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/thread_local.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+// The first 8 characters of sha1 of "ScopedBlockingCall".
+// echo -n "ScopedBlockingCall" | sha1sum
+constexpr uint32_t kActivityTrackerId = 0x11be9915;
+
+LazyInstance<ThreadLocalPointer<internal::BlockingObserver>>::Leaky
+    tls_blocking_observer = LAZY_INSTANCE_INITIALIZER;
+
+// Last ScopedBlockingCall instantiated on this thread.
+LazyInstance<ThreadLocalPointer<internal::UncheckedScopedBlockingCall>>::Leaky
+    tls_last_scoped_blocking_call = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+void SetBlockingObserverForCurrentThread(BlockingObserver* blocking_observer) {
+  DCHECK(!tls_blocking_observer.Get().Get());
+  tls_blocking_observer.Get().Set(blocking_observer);
+}
+
+void ClearBlockingObserverForCurrentThread() {
+  tls_blocking_observer.Get().Set(nullptr);
+}
+
+ScopedClearBlockingObserverForTesting::ScopedClearBlockingObserverForTesting()
+    : blocking_observer_(tls_blocking_observer.Get().Get()) {
+  tls_blocking_observer.Get().Set(nullptr);
+}
+
+ScopedClearBlockingObserverForTesting::
+    ~ScopedClearBlockingObserverForTesting() {
+  DCHECK(!tls_blocking_observer.Get().Get());
+  tls_blocking_observer.Get().Set(blocking_observer_);
+}
+
+UncheckedScopedBlockingCall::UncheckedScopedBlockingCall(
+    const Location& from_here,
+    BlockingType blocking_type)
+    : blocking_observer_(tls_blocking_observer.Get().Get()),
+      previous_scoped_blocking_call_(tls_last_scoped_blocking_call.Get().Get()),
+      is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
+                     (previous_scoped_blocking_call_ &&
+                      previous_scoped_blocking_call_->is_will_block_)),
+      scoped_activity_(from_here, 0, kActivityTrackerId, 0) {
+  tls_last_scoped_blocking_call.Get().Set(this);
+
+  if (blocking_observer_) {
+    if (!previous_scoped_blocking_call_) {
+      blocking_observer_->BlockingStarted(blocking_type);
+    } else if (blocking_type == BlockingType::WILL_BLOCK &&
+               !previous_scoped_blocking_call_->is_will_block_) {
+      blocking_observer_->BlockingTypeUpgraded();
+    }
+  }
+
+  if (scoped_activity_.IsRecorded()) {
+    // Also record the data for extended crash reporting.
+    const base::TimeTicks now = base::TimeTicks::Now();
+    auto& user_data = scoped_activity_.user_data();
+    user_data.SetUint("timestamp_us", now.since_origin().InMicroseconds());
+    user_data.SetUint("blocking_type", static_cast<uint64_t>(blocking_type));
+  }
+}
+
+UncheckedScopedBlockingCall::~UncheckedScopedBlockingCall() {
+  // TLS affects result of GetLastError() on Windows. ScopedClearLastError
+  // prevents side effect.
+  base::internal::ScopedClearLastError save_last_error;
+  DCHECK_EQ(this, tls_last_scoped_blocking_call.Get().Get());
+  tls_last_scoped_blocking_call.Get().Set(previous_scoped_blocking_call_);
+  if (blocking_observer_ && !previous_scoped_blocking_call_)
+    blocking_observer_->BlockingEnded();
+}
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/threading/scoped_blocking_call_internal.h b/base/threading/scoped_blocking_call_internal.h
new file mode 100644
index 0000000..50d82a5
--- /dev/null
+++ b/base/threading/scoped_blocking_call_internal.h
@@ -0,0 +1,87 @@
+// Copyright 2020 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 BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
+#define BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
+
+#include "base/base_export.h"
+#include "base/debug/activity_tracker.h"
+#include "base/macros.h"
+
+namespace base {
+
+enum class BlockingType;
+
+// Implementation details of types in scoped_blocking_call.h and classes for a
+// few key //base types to observe and react to blocking calls.
+namespace internal {
+
+// Interface for an observer to be informed when a thread enters or exits
+// the scope of ScopedBlockingCall objects.
+class BASE_EXPORT BlockingObserver {
+ public:
+  virtual ~BlockingObserver() = default;
+
+  // Invoked when a ScopedBlockingCall is instantiated on the observed thread
+  // where there wasn't an existing ScopedBlockingCall.
+  virtual void BlockingStarted(BlockingType blocking_type) = 0;
+
+  // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the
+  // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a
+  // WILL_BLOCK ScopedBlockingCall.
+  virtual void BlockingTypeUpgraded() = 0;
+
+  // Invoked when the last ScopedBlockingCall on the observed thread is
+  // destroyed.
+  virtual void BlockingEnded() = 0;
+};
+
+// Registers |blocking_observer| on the current thread. It is invalid to call
+// this on a thread where there is an active ScopedBlockingCall.
+BASE_EXPORT void SetBlockingObserverForCurrentThread(
+    BlockingObserver* blocking_observer);
+
+BASE_EXPORT void ClearBlockingObserverForCurrentThread();
+
+// Unregisters the |blocking_observer| on the current thread within its scope.
+// Used in ThreadPool tests to prevent calls to //base sync primitives from
+// affecting the thread pool capacity.
+class BASE_EXPORT ScopedClearBlockingObserverForTesting {
+ public:
+  ScopedClearBlockingObserverForTesting();
+  ~ScopedClearBlockingObserverForTesting();
+
+ private:
+  BlockingObserver* const blocking_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedClearBlockingObserverForTesting);
+};
+
+// Common implementation class for both ScopedBlockingCall and
+// ScopedBlockingCallWithBaseSyncPrimitives without assertions.
+class BASE_EXPORT UncheckedScopedBlockingCall {
+ public:
+  explicit UncheckedScopedBlockingCall(const Location& from_here,
+                                       BlockingType blocking_type);
+  ~UncheckedScopedBlockingCall();
+
+ private:
+  internal::BlockingObserver* const blocking_observer_;
+
+  // Previous ScopedBlockingCall instantiated on this thread.
+  UncheckedScopedBlockingCall* const previous_scoped_blocking_call_;
+
+  // Whether the BlockingType of the current thread was WILL_BLOCK after this
+  // ScopedBlockingCall was instantiated.
+  const bool is_will_block_;
+
+  base::debug::ScopedActivity scoped_activity_;
+
+  DISALLOW_COPY_AND_ASSIGN(UncheckedScopedBlockingCall);
+};
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
diff --git a/base/threading/scoped_blocking_call_unittest.cc b/base/threading/scoped_blocking_call_unittest.cc
index 4c703b5..2c71e5a 100644
--- a/base/threading/scoped_blocking_call_unittest.cc
+++ b/base/threading/scoped_blocking_call_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/macros.h"
 #include "base/test/gtest_util.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index b7b71fb..af33962 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200325.2.1
\ No newline at end of file
+0.20200325.3.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index b7b71fb..af33962 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200325.2.1
\ No newline at end of file
+0.20200325.3.1
\ No newline at end of file
diff --git a/cc/paint/display_item_list.h b/cc/paint/display_item_list.h
index 7a771816..3054d27c 100644
--- a/cc/paint/display_item_list.h
+++ b/cc/paint/display_item_list.h
@@ -168,7 +168,6 @@
 
   int NumSlowPaths() const { return paint_op_buffer_.numSlowPaths(); }
   bool HasNonAAPaint() const { return paint_op_buffer_.HasNonAAPaint(); }
-  bool HasText() const { return paint_op_buffer_.HasText(); }
 
   // This gives the total number of PaintOps.
   size_t TotalOpCount() const { return paint_op_buffer_.total_op_count(); }
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index ce9ede25..6417c049 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -2342,10 +2342,6 @@
   return record->HasDiscardableImages();
 }
 
-bool DrawRecordOp::HasText() const {
-  return record->HasText();
-}
-
 DrawTextBlobOp::DrawTextBlobOp() : PaintOpWithFlags(kType) {}
 
 DrawTextBlobOp::DrawTextBlobOp(sk_sp<SkTextBlob> blob,
@@ -2383,9 +2379,7 @@
     default;
 
 PaintOpBuffer::PaintOpBuffer()
-    : has_non_aa_paint_(false),
-      has_discardable_images_(false),
-      has_text_(false) {}
+    : has_non_aa_paint_(false), has_discardable_images_(false) {}
 
 PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) {
   *this = std::move(other);
@@ -2405,7 +2399,6 @@
   subrecord_op_count_ = other.subrecord_op_count_;
   has_non_aa_paint_ = other.has_non_aa_paint_;
   has_discardable_images_ = other.has_discardable_images_;
-  has_text_ = other.has_text_;
 
   // Make sure the other pob can destruct safely.
   other.used_ = 0;
@@ -2427,7 +2420,6 @@
   subrecord_bytes_used_ = 0;
   subrecord_op_count_ = 0;
   has_discardable_images_ = false;
-  has_text_ = false;
 }
 
 // When |op| is a nested PaintOpBuffer, this returns the PaintOp inside
@@ -2703,8 +2695,6 @@
     return false;
   if (has_discardable_images_ != other.has_discardable_images_)
     return false;
-  if (has_text_ != other.has_text_)
-    return false;
 
   auto left_iter = Iterator(this);
   auto right_iter = Iterator(&other);
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index e6b2a9ac..ef56440 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -256,8 +256,6 @@
   bool HasDiscardableImages() const { return false; }
   bool HasDiscardableImagesFromFlags() const { return false; }
 
-  bool HasText() const { return false; }
-
   // Returns the number of bytes used by this op in referenced sub records
   // and display lists.  This doesn't count other objects like paths or blobs.
   size_t AdditionalBytesUsed() const { return 0; }
@@ -674,7 +672,6 @@
   bool HasDiscardableImages() const;
   int CountSlowPaths() const;
   bool HasNonAAPaint() const;
-  bool HasText() const;
   HAS_SERIALIZATION_FUNCTIONS();
 
   sk_sp<const PaintRecord> record;
@@ -763,7 +760,6 @@
                               const PlaybackParams& params);
   bool IsValid() const { return flags.IsValid(); }
   static bool AreEqual(const PaintOp* left, const PaintOp* right);
-  bool HasText() const { return true; }
   HAS_SERIALIZATION_FUNCTIONS();
 
   sk_sp<SkTextBlob> blob;
@@ -988,7 +984,6 @@
   int numSlowPaths() const { return num_slow_paths_; }
   bool HasNonAAPaint() const { return has_non_aa_paint_; }
   bool HasDiscardableImages() const { return has_discardable_images_; }
-  bool HasText() const { return has_text_; }
 
   bool operator==(const PaintOpBuffer& other) const;
   bool operator!=(const PaintOpBuffer& other) const {
@@ -1050,8 +1045,6 @@
     has_discardable_images_ |= op->HasDiscardableImages();
     has_discardable_images_ |= op->HasDiscardableImagesFromFlags();
 
-    has_text_ |= (op->HasText());
-
     subrecord_bytes_used_ += op->AdditionalBytesUsed();
     subrecord_op_count_ += op->AdditionalOpCount();
   }
@@ -1279,7 +1272,6 @@
 
   bool has_non_aa_paint_ : 1;
   bool has_discardable_images_ : 1;
-  bool has_text_ : 1;
 };
 
 }  // namespace cc
diff --git a/cc/raster/raster_source.cc b/cc/raster/raster_source.cc
index 640801c..58d7ea0 100644
--- a/cc/raster/raster_source.cc
+++ b/cc/raster/raster_source.cc
@@ -220,10 +220,6 @@
   return recorded_viewport_;
 }
 
-bool RasterSource::HasText() const {
-  return display_list_ && display_list_->HasText();
-}
-
 void RasterSource::AsValueInto(base::trace_event::TracedValue* array) const {
   if (display_list_.get())
     viz::TracedValue::AppendIDRef(display_list_.get(), array);
diff --git a/cc/raster/raster_source.h b/cc/raster/raster_source.h
index 33b3564..c0686de 100644
--- a/cc/raster/raster_source.h
+++ b/cc/raster/raster_source.h
@@ -106,9 +106,6 @@
   // Valid rectangle in which everything is recorded and can be rastered from.
   gfx::Rect RecordedViewport() const;
 
-  // Returns true if this raster source may try and draw text.
-  bool HasText() const;
-
   // Tracing functionality.
   void DidBeginTracing();
   void AsValueInto(base::trace_event::TracedValue* array) const;
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 25fb219..423b30f8 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1508,17 +1508,8 @@
   const char* client_name = GetClientNameForMetrics();
   if (client_name) {
     size_t total_gpu_memory_for_tilings_in_bytes = 0;
-    int layers_with_text_count = 0;
-    int layers_with_text_no_lcd_text_count = 0;
-    for (const PictureLayerImpl* layer : active_tree()->picture_layers()) {
+    for (const PictureLayerImpl* layer : active_tree()->picture_layers())
       total_gpu_memory_for_tilings_in_bytes += layer->GPUMemoryUsageInBytes();
-      if (layer->GetRasterSource()->HasText()) {
-        layers_with_text_count++;
-        if (!layer->can_use_lcd_text()) {
-          layers_with_text_no_lcd_text_count++;
-        }
-      }
-    }
 
     UMA_HISTOGRAM_CUSTOM_COUNTS(
         base::StringPrintf("Compositing.%s.NumActiveLayers", client_name),
@@ -1530,34 +1521,6 @@
         base::saturated_cast<int>(active_tree_->picture_layers().size()), 1,
         400, 20);
 
-    if (layers_with_text_count > 0) {
-      int percent =
-          100.0 * layers_with_text_no_lcd_text_count / layers_with_text_count;
-
-      if (layers_with_text_count < 10) {
-        UMA_HISTOGRAM_PERCENTAGE(
-            base::StringPrintf(
-                "Compositing.%s.PercentPictureLayersWithTextButLCDTextDisabled."
-                "LessThan10",
-                client_name),
-            percent);
-      } else if (layers_with_text_count <= 30) {
-        UMA_HISTOGRAM_PERCENTAGE(
-            base::StringPrintf(
-                "Compositing.%s.PercentPictureLayersWithTextButLCDTextDisabled."
-                "10To30",
-                client_name),
-            percent);
-      } else {
-        UMA_HISTOGRAM_PERCENTAGE(
-            base::StringPrintf(
-                "Compositing.%s."
-                "PercentPictureLayersWithTextButLCDTextDisabled.MoreThan30",
-                client_name),
-            percent);
-      }
-    }
-
     // TODO(yigu): Maybe we should use the same check above. Need to figure out
     // why exactly we skip 0.
     if (!active_tree()->picture_layers().empty()) {
diff --git a/chrome/VERSION b/chrome/VERSION
index 607ed5d..ac1bcbe 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=83
 MINOR=0
-BUILD=4096
+BUILD=4097
 PATCH=0
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index e16decd..60f077d 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -21,6 +21,7 @@
   "javatests/src/org/chromium/chrome/browser/InstantStartTest.java",
   "javatests/src/org/chromium/chrome/browser/IntentHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java",
+  "javatests/src/org/chromium/chrome/browser/LauncherShortcutTest.java",
   "javatests/src/org/chromium/chrome/browser/MainActivityWithURLTest.java",
   "javatests/src/org/chromium/chrome/browser/MockSafeBrowsingApiHandler.java",
   "javatests/src/org/chromium/chrome/browser/NavigateTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java
index e79d3c9..bcf8014 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LauncherShortcutActivity.java
@@ -33,8 +33,8 @@
     public static final String ACTION_OPEN_NEW_TAB = "chromium.shortcut.action.OPEN_NEW_TAB";
     public static final String ACTION_OPEN_NEW_INCOGNITO_TAB =
             "chromium.shortcut.action.OPEN_NEW_INCOGNITO_TAB";
-    private static final String DYNAMIC_OPEN_NEW_INCOGNITO_TAB_ID =
-            "dynamic-new-incognito-tab-shortcut";
+    @VisibleForTesting
+    static final String DYNAMIC_OPEN_NEW_INCOGNITO_TAB_ID = "dynamic-new-incognito-tab-shortcut";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -71,15 +71,18 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return;
 
         SharedPreferencesManager preferences = SharedPreferencesManager.getInstance();
-        if (IncognitoUtils.isIncognitoModeEnabled()) {
+        boolean incognitoEnabled = IncognitoUtils.isIncognitoModeEnabled();
+        boolean incognitoShortcutAdded =
+                preferences.readBoolean(ChromePreferenceKeys.INCOGNITO_SHORTCUT_ADDED, false);
+
+        if (incognitoEnabled && !incognitoShortcutAdded) {
             boolean success = LauncherShortcutActivity.addIncognitoLauncherShortcut(context);
 
             // Save a shared preference indicating the incognito shortcut has been added.
             if (success) {
                 preferences.writeBoolean(ChromePreferenceKeys.INCOGNITO_SHORTCUT_ADDED, true);
             }
-        } else if (preferences.readBoolean(ChromePreferenceKeys.INCOGNITO_SHORTCUT_ADDED, false)
-                && !IncognitoUtils.isIncognitoModeEnabled()) {
+        } else if (!incognitoEnabled && incognitoShortcutAdded) {
             LauncherShortcutActivity.removeIncognitoLauncherShortcut(context);
             preferences.writeBoolean(ChromePreferenceKeys.INCOGNITO_SHORTCUT_ADDED, false);
         }
@@ -88,7 +91,7 @@
     /**
      * Adds a "New incognito tab" dynamic launcher shortcut.
      * @param context The context used to retrieve the system {@link ShortcutManager}.
-     * @return True if addint the shortcut has succeeded. False if the call fails due to rate
+     * @return True if adding the shortcut has succeeded. False if the call fails due to rate
      *         limiting. See {@link ShortcutManager#addDynamicShortcuts}.
      */
     @TargetApi(Build.VERSION_CODES.N_MR1)
@@ -131,8 +134,7 @@
      *                                     LauncherShortcutActivity.
      * @return An intent for ChromeLauncherActivity that will open a new regular or incognito tab.
      */
-    @VisibleForTesting
-    public static Intent getChromeLauncherActivityIntent(
+    private static Intent getChromeLauncherActivityIntent(
             Context context, String launcherShortcutIntentAction) {
         Intent newIntent = IntentHandler.createTrustedOpenNewTabIntent(context,
                 launcherShortcutIntentAction.equals(ACTION_OPEN_NEW_INCOGNITO_TAB));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 832398c..067d8c9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -159,6 +159,15 @@
     }
 
     /**
+     * @param gurl The GURL to check whether it is for the NTP.
+     * @return Whether the passed in URL is used to render the NTP.
+     */
+    public static boolean isNTPUrl(GURL gurl) {
+        if (!gurl.isValid() || !UrlUtilities.isInternalScheme(gurl)) return false;
+        return UrlConstants.NTP_HOST.equals(gurl.getHost());
+    }
+
+    /**
      * @param url The URL to check whether it is for the NTP.
      * @return Whether the passed in URL is used to render the NTP.
      */
@@ -169,8 +178,7 @@
         // We need to fixup the URL to handle about: schemes and transform them into the equivalent
         // chrome:// scheme so that GURL parses the host correctly.
         GURL gurl = UrlFormatter.fixupUrl(url);
-        if (!gurl.isValid() || !UrlUtilities.isInternalScheme(gurl)) return false;
-        return UrlConstants.NTP_HOST.equals(gurl.getHost());
+        return isNTPUrl(gurl);
     }
 
     protected class NewTabPageManagerImpl
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
index af1b0e4..75f406c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabImpl;
 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
+import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -253,6 +254,9 @@
      * property model.
      **/
     private void reconcileVisualState(boolean showStatusIconWhenFocused) {
+        // No reconciliation is needed on tablet because the status icon is always shown.
+        if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(mStatusView.getContext())) return;
+
         // State requirements:
         // - The ToolbarDataProvider and views are not null.
         // - The status icon will be shown when focused.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/LauncherShortcutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/LauncherShortcutTest.java
new file mode 100644
index 0000000..768f29f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/LauncherShortcutTest.java
@@ -0,0 +1,172 @@
+// Copyright 2020 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;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.params.ParameterAnnotations;
+import org.chromium.base.test.params.ParameterProvider;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.MinAndroidSdkLevel;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.incognito.IncognitoUtils;
+import org.chromium.chrome.browser.ntp.NewTabPage;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabCreationState;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
+import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests for Android NMR1 launcher shortcuts.
+ */
+// clang-format off
+@RunWith(ParameterizedRunner.class)
+@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@TargetApi(VERSION_CODES.N_MR1)
+@MinAndroidSdkLevel(Build.VERSION_CODES.N_MR1)
+public class LauncherShortcutTest {
+    // clang-format on
+
+    /**
+     * Used for parameterized tests to toggle whether an incognito or regular tab is created.
+     */
+    public static class IncognitoParams implements ParameterProvider {
+        @Override
+        public Iterable<ParameterSet> getParameters() {
+            return Arrays.asList(new ParameterSet().value(false).name("RegularTab"),
+                    new ParameterSet().value(true).name("IncognitoTab"));
+        }
+    }
+
+    @Rule
+    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+
+    private TabModelSelector mTabModelSelector;
+    private CallbackHelper mTabAddedCallback = new CallbackHelper();
+
+    @Before
+    public void setUp() {
+        mActivityTestRule.startMainActivityOnBlankPage();
+        mTabModelSelector = mActivityTestRule.getActivity().getTabModelSelector();
+
+        TabModelSelectorObserver tabModelSelectorObserver = new EmptyTabModelSelectorObserver() {
+            @Override
+            public void onNewTabCreated(Tab tab, @TabCreationState int creationState) {
+                mTabAddedCallback.notifyCalled();
+            }
+        };
+        mTabModelSelector.addObserver(tabModelSelectorObserver);
+    }
+
+    @Test
+    @MediumTest
+    @ParameterAnnotations.UseMethodParameter(IncognitoParams.class)
+    public void testLauncherShortcut(boolean incognito) throws Exception {
+        int initialTabCount = mTabModelSelector.getTotalTabCount();
+
+        Intent intent =
+                new Intent(incognito ? LauncherShortcutActivity.ACTION_OPEN_NEW_INCOGNITO_TAB
+                                     : LauncherShortcutActivity.ACTION_OPEN_NEW_TAB);
+        intent.setClass(mActivityTestRule.getActivity(), LauncherShortcutActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+
+        mTabAddedCallback.waitForCallback(0);
+
+        // Verify NTP was created.
+
+        Tab activityTab = TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getActivityTab());
+        Assert.assertEquals("Incorrect tab launch type.", TabLaunchType.FROM_LAUNCHER_SHORTCUT,
+                activityTab.getLaunchType());
+        Assert.assertTrue("Tab should be an NTP. Tab url: " + activityTab.getUrl(),
+                NewTabPage.isNTPUrl(activityTab.getUrl()));
+
+        // Verify tab model.
+        Assert.assertEquals("Incorrect tab model selected.", incognito,
+                mTabModelSelector.isIncognitoSelected());
+        Assert.assertEquals("Incorrect total tab count.", initialTabCount + 1,
+                mTabModelSelector.getTotalTabCount());
+        Assert.assertEquals("Incorrect normal tab count.",
+                incognito ? initialTabCount : initialTabCount + 1,
+                mTabModelSelector.getModel(false).getCount());
+        Assert.assertEquals("Incorrect incognito tab count.", incognito ? 1 : 0,
+                mTabModelSelector.getModel(true).getCount());
+    }
+
+    @Test(expected = TimeoutException.class)
+    @MediumTest
+    public void testInvalidIntent() throws TimeoutException {
+        Intent intent = new Intent("fooAction");
+        intent.setClass(mActivityTestRule.getActivity(), LauncherShortcutActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+
+        mTabAddedCallback.waitForCallback(0);
+    }
+
+    @Test
+    @MediumTest
+    public void testManifestShortcuts() {
+        ShortcutManager shortcutManager =
+                mActivityTestRule.getActivity().getSystemService(ShortcutManager.class);
+        List<ShortcutInfo> shortcuts = shortcutManager.getManifestShortcuts();
+        Assert.assertEquals("Incorrect number of manifest shortcuts.", 1, shortcuts.size());
+        Assert.assertEquals(
+                "Incorrect manifest shortcut id.", "new-tab-shortcut", shortcuts.get(0).getId());
+    }
+
+    @Test
+    @SmallTest
+    public void testDynamicShortcuts() {
+        IncognitoUtils.setEnabledForTesting(true);
+        LauncherShortcutActivity.updateIncognitoShortcut(mActivityTestRule.getActivity());
+        ShortcutManager shortcutManager =
+                mActivityTestRule.getActivity().getSystemService(ShortcutManager.class);
+        List<ShortcutInfo> shortcuts = shortcutManager.getDynamicShortcuts();
+        Assert.assertEquals("Incorrect number of dynamic shortcuts.", 1, shortcuts.size());
+        Assert.assertEquals("Incorrect dynamic shortcut id.",
+                LauncherShortcutActivity.DYNAMIC_OPEN_NEW_INCOGNITO_TAB_ID,
+                shortcuts.get(0).getId());
+
+        IncognitoUtils.setEnabledForTesting(false);
+        LauncherShortcutActivity.updateIncognitoShortcut(mActivityTestRule.getActivity());
+        shortcuts = shortcutManager.getDynamicShortcuts();
+        Assert.assertEquals("Incorrect number of dynamic shortcuts.", 0, shortcuts.size());
+
+        IncognitoUtils.setEnabledForTesting(true);
+        LauncherShortcutActivity.updateIncognitoShortcut(mActivityTestRule.getActivity());
+        shortcuts = shortcutManager.getDynamicShortcuts();
+        Assert.assertEquals("Incorrect number of dynamic shortcuts after re-enabling incognito.", 1,
+                shortcuts.size());
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index 3dd18534..8eb13b47 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -31,7 +31,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -446,11 +445,10 @@
 
     @Test
     @SmallTest
-    @FlakyTest(message = "https://crbug.com/1064092")
     public void testRealPromoDialogDismissWithoutSelection() throws Exception {
         // Start the Activity.  It should pause when the promo dialog appears.
         mTestDelegate.shouldShowRealSearchDialog = true;
-        startSearchActivity();
+        SearchActivity activity = startSearchActivity();
         mTestDelegate.shouldDelayNativeInitializationCallback.waitForCallback(0);
         mTestDelegate.showSearchEngineDialogIfNeededCallback.waitForCallback(0);
         mTestDelegate.onPromoDialogShownCallback.waitForCallback(0);
@@ -460,12 +458,24 @@
         mTestDelegate.shownPromoDialog.dismiss();
 
         // SearchActivity should realize the failure case and prevent the user from using it.
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
+        CriteriaHelper.pollUiThread(new Criteria() {
             @Override
-            public Integer call() {
-                return ApplicationStatus.getRunningActivities().size();
+            public boolean isSatisfied() {
+                List<Activity> activities = ApplicationStatus.getRunningActivities();
+                if (activities.isEmpty()) return true;
+
+                if (activities.size() != 1) {
+                    updateFailureReason("Multiple non-destroyed activities: " + activities);
+                    return false;
+                }
+                if (activities.get(0) != activity) {
+                    updateFailureReason("Remaining activity is not the search activity under test: "
+                            + activities.get(0));
+                }
+                updateFailureReason("Search activity has not called finish()");
+                return activity.isFinishing();
             }
-        }));
+        });
         Assert.assertEquals(
                 1, mTestDelegate.shouldDelayNativeInitializationCallback.getCallCount());
         Assert.assertEquals(1, mTestDelegate.showSearchEngineDialogIfNeededCallback.getCallCount());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
index 3ee6030..0fd5b1f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappModeTest.java
@@ -187,6 +187,7 @@
     @Test
     @MediumTest
     @Feature({"Webapps"})
+    @DisabledTest(message = "crbug.com/1064395")
     public void testBringTabToFront() {
         // Start the WebappActivity.
         final WebappActivity firstActivity =
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1f0d8d8..1c1052e7 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4201,12 +4201,6 @@
       <message name="IDS_EXTENSION_INVALID_IMAGE_PATH" desc="">
         Could not load '<ph name="IMAGE_PATH">$1<ex>/path/to/file</ex></ph>' for theme.
       </message>
-      <message name="IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED" desc="">
-        Could not load icon '<ph name="ICON">$1<ex>icon.png</ex></ph>' for page action.
-      </message>
-      <message name="IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED" desc="">
-        Could not load icon '<ph name="ICON">$1<ex>icon.png</ex></ph>' for browser action.
-      </message>
 
       <if expr="chromeos">
         <message name="IDS_EXTENSION_NOTIFICATION_INSTALLED" desc="Notification message shown in the message center when an application is successfully install.">
@@ -10058,16 +10052,16 @@
 
     <!-- New Native File System permission dialog -->
     <message name="IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT" desc="Text of the dialog for confirming origin scoped write access to files using the Native File System API">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit <ph name="FILENAME">$2<ex>README.md</ex></ph> until you close all <ph name="ORIGIN">$1</ph> tabs
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit <ph name="FILENAME">$2<ex>README.md</ex></ph> until you close all tabs for this site
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT" desc="Text of the dialog for confirming origin scoped write access to a directory using the Native File System API">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph> until you close all <ph name="ORIGIN">$1</ph> tabs
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph> until you close all tabs for this site
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT" desc="Text of dialog for confirming read access to files using the Native File System API">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view <ph name="FILENAME">$2<ex>README.md</ex></ph> until you close all <ph name="ORIGIN">$1</ph> tabs
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view <ph name="FILENAME">$2<ex>README.md</ex></ph> until you close all tabs for this site
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT" desc="Text of dialog asking user if they intended to share a particular directory">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph> until you close all <ph name="ORIGIN">$1</ph> tabs
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph> until you close all tabs for this site
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_EDIT_FILE_PERMISSION_TITLE" desc="Title of dialog asking user to confirm giving read and write access to a file using the Native File System API">
       Let site edit <ph name="FILE_NAME">$1<ex>README.md</ex></ph>?
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT.png.sha1
index 0bef3ea..4c79bdf8e 100644
--- a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT.png.sha1
@@ -1 +1 @@
-be0df6426347b47cfb2c0a7276c98ab9a37f601d
\ No newline at end of file
+ed38009cbc31574a12f4db2209c50ba57511d876
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT.png.sha1
index 8a9939b..7dea339 100644
--- a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT.png.sha1
@@ -1 +1 @@
-5d6fba335cb335d431ceb56b3ba4dcdcd79e27ae
\ No newline at end of file
+f185030c112a289402e38a2ffc64ee02736f5559
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1
index fd0310d..30584b2 100644
--- a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1
@@ -1 +1 @@
-2b77a0884dcaaca8066c65b83b7ab0babff3bb3f
\ No newline at end of file
+db3080b701d49f1f90a56a9b0c6b91aadf3d32a4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT.png.sha1
index 3eead001..b7fbaed 100644
--- a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT.png.sha1
@@ -1 +1 @@
-d1933009afb448aefd43df15f9b2c8f66b9354b6
\ No newline at end of file
+7c1a9859b187e6449a0a4b140c1e0f6a5cfa4f59
\ No newline at end of file
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 07b593fa..2069ad4 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -115,6 +115,7 @@
     "tab_audio_rounded.icon",
     "tab_bluetooth_connected.icon",
     "tab_close_normal.icon",
+    "tab_group.icon",
     "tab_media_capturing.icon",
     "tab_media_capturing_with_arrow.icon",
     "tab_media_recording.icon",
diff --git a/chrome/app/vector_icons/tab_group.icon b/chrome/app/vector_icons/tab_group.icon
new file mode 100644
index 0000000..de6ce8d
--- /dev/null
+++ b/chrome/app/vector_icons/tab_group.icon
@@ -0,0 +1,5 @@
+// Copyright 2020 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.

+

+CIRCLE, 24, 24, 24
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 804dedce..a71e892 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1825,6 +1825,8 @@
     "sync/trusted_vault_client_android.h",
     "sync/user_event_service_factory.cc",
     "sync/user_event_service_factory.h",
+    "tab/state/tab_state_db.cc",
+    "tab/state/tab_state_db.h",
     "tab_contents/navigation_metrics_recorder.cc",
     "tab_contents/navigation_metrics_recorder.h",
     "tab_contents/tab_util.cc",
@@ -1981,6 +1983,7 @@
     ":ntp_background_proto",
     ":permissions_proto",
     ":resource_prefetch_predictor_proto",
+    ":tab_state_db_content_proto",
     "//base:i18n",
     "//base/allocator:buildflags",
     "//base/util/memory_pressure:memory_pressure",
@@ -5568,6 +5571,10 @@
   sources = [ "availability/proto/availability_prober_cache_entry.proto" ]
 }
 
+proto_library("tab_state_db_content_proto") {
+  sources = [ "tab/state/tab_state_db_content.proto" ]
+}
+
 proto_library("resource_prefetch_predictor_proto") {
   sources = [ "predictors/resource_prefetch_predictor.proto" ]
 }
@@ -5921,6 +5928,8 @@
       "chromeos/input_method/mock_input_method_engine.h",
       "chromeos/input_method/mock_input_method_manager_impl.cc",
       "chromeos/input_method/mock_input_method_manager_impl.h",
+      "chromeos/input_method/mock_suggestion_window_controller.cc",
+      "chromeos/input_method/mock_suggestion_window_controller.h",
       "chromeos/login/screens/mock_device_disabled_screen_view.cc",
       "chromeos/login/screens/mock_device_disabled_screen_view.h",
       "chromeos/login/session/user_session_manager_test_api.cc",
diff --git a/chrome/browser/android/vr/arcore_device/arcore.h b/chrome/browser/android/vr/arcore_device/arcore.h
index 4a7ffbd..c0edcdc1 100644
--- a/chrome/browser/android/vr/arcore_device/arcore.h
+++ b/chrome/browser/android/vr/arcore_device/arcore.h
@@ -45,6 +45,10 @@
   // when the camera image was updated successfully.
   virtual mojom::VRPosePtr Update(bool* camera_updated) = 0;
 
+  // Camera image timestamp. This returns TimeDelta instead of TimeTicks since
+  // ARCore internally uses an arbitrary and unspecified time base.
+  virtual base::TimeDelta GetFrameTimestamp() = 0;
+
   // Return latest estimate for the floor height.
   virtual float GetEstimatedFloorHeight() = 0;
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index aeae19a..96480c23 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -363,13 +363,13 @@
   base::TimeTicks arcore_update_started = base::TimeTicks::Now();
   mojom::VRPosePtr pose = arcore_->Update(&camera_updated);
   base::TimeTicks now = base::TimeTicks::Now();
-  if (!arcore_update_completed_.is_null()) {
-    arcore_update_interval_ = now - arcore_update_completed_;
-    arcore_update_next_expected_ = now + arcore_update_interval_;
+  base::TimeDelta frame_timestamp = arcore_->GetFrameTimestamp();
+  if (!arcore_last_frame_timestamp_.is_zero()) {
+    arcore_frame_interval_ = frame_timestamp - arcore_last_frame_timestamp_;
+    arcore_update_next_expected_ = now + arcore_frame_interval_;
   }
-  arcore_update_completed_ = now;
-  base::TimeDelta arcore_update_elapsed =
-      arcore_update_completed_ - arcore_update_started;
+  arcore_last_frame_timestamp_ = frame_timestamp;
+  base::TimeDelta arcore_update_elapsed = now - arcore_update_started;
   TRACE_COUNTER1("gpu", "ARCore update elapsed (ms)",
                  arcore_update_elapsed.InMilliseconds());
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 0f34b89..7847505e 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -232,9 +232,9 @@
 
   bool restrict_frame_data_ = false;
 
-  base::TimeTicks arcore_update_completed_;
   base::TimeTicks arcore_update_next_expected_;
-  base::TimeDelta arcore_update_interval_;
+  base::TimeDelta arcore_last_frame_timestamp_;
+  base::TimeDelta arcore_frame_interval_;
   FPSMeter fps_meter_;
 
   mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index 75b85da..b0088eb 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -465,6 +465,15 @@
   return GetMojomVRPoseFromArPose(arcore_session_.get(), arcore_pose.get());
 }
 
+base::TimeDelta ArCoreImpl::GetFrameTimestamp() {
+  DCHECK(arcore_session_.is_valid());
+  DCHECK(arcore_frame_.is_valid());
+  int64_t out_timestamp_ns;
+  ArFrame_getTimestamp(arcore_session_.get(), arcore_frame_.get(),
+                       &out_timestamp_ns);
+  return base::TimeDelta::FromNanoseconds(out_timestamp_ns);
+}
+
 mojom::XRPlaneDetectionDataPtr ArCoreImpl::GetDetectedPlanesData() {
   DVLOG(2) << __func__;
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.h b/chrome/browser/android/vr/arcore_device/arcore_impl.h
index 6b65de5..1ff1125d 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.h
@@ -64,6 +64,7 @@
       const base::span<const float> uvs) override;
   gfx::Transform GetProjectionMatrix(float near, float far) override;
   mojom::VRPosePtr Update(bool* camera_updated) override;
+  base::TimeDelta GetFrameTimestamp() override;
 
   mojom::XRPlaneDetectionDataPtr GetDetectedPlanesData() override;
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_shim.cc b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
index c2bee457..3a29a78 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_shim.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
@@ -34,6 +34,7 @@
   DO(ArFrame_create)                                               \
   DO(ArFrame_destroy)                                              \
   DO(ArFrame_getLightEstimate)                                     \
+  DO(ArFrame_getTimestamp)                                         \
   DO(ArFrame_getUpdatedAnchors)                                    \
   DO(ArFrame_getUpdatedTrackables)                                 \
   DO(ArFrame_hitTestRay)                                           \
@@ -275,6 +276,13 @@
                                                      out_light_estimate);
 }
 
+void ArFrame_getTimestamp(const ArSession* session,
+                          const ArFrame* frame,
+                          int64_t* out_timestamp_ns) {
+  return g_arcore_api->impl_ArFrame_getTimestamp(session, frame,
+                                                 out_timestamp_ns);
+}
+
 void ArFrame_getUpdatedAnchors(const ArSession* session,
                                const ArFrame* frame,
                                ArAnchorList* out_anchor_list) {
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index 4736fbfd..3db6491 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -196,6 +196,10 @@
   return pose;
 }
 
+base::TimeDelta FakeArCore::GetFrameTimestamp() {
+  return base::TimeTicks::Now() - base::TimeTicks();
+}
+
 float FakeArCore::GetEstimatedFloorHeight() {
   return 2.0;
 }
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h
index af17bea..c5343c99 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.h
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -31,6 +31,7 @@
       const base::span<const float> uvs) override;
   gfx::Transform GetProjectionMatrix(float near, float far) override;
   mojom::VRPosePtr Update(bool* camera_updated) override;
+  base::TimeDelta GetFrameTimestamp() override;
 
   void Pause() override;
   void Resume() override;
diff --git a/chrome/browser/apps/app_shim/BUILD.gn b/chrome/browser/apps/app_shim/BUILD.gn
index b6958c4..07bc914 100644
--- a/chrome/browser/apps/app_shim/BUILD.gn
+++ b/chrome/browser/apps/app_shim/BUILD.gn
@@ -14,8 +14,6 @@
     "app_shim_listener.mm",
     "app_shim_manager_mac.cc",
     "app_shim_manager_mac.h",
-    "app_shim_registry_mac.cc",
-    "app_shim_registry_mac.h",
     "app_shim_termination_manager.cc",
     "app_shim_termination_manager.h",
     "mach_bootstrap_acceptor.cc",
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
index b94f0ba..d8086f54 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_listener.h"
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_termination_manager.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
@@ -37,6 +36,7 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_window.h"
 #include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/components/web_app_shortcut_mac.h"
 #include "components/crx_file/id_util.h"
diff --git a/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc b/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
index 46973767..d849f47 100644
--- a/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
+++ b/chrome/browser/apps/app_shim/app_shim_manager_mac_unittest.cc
@@ -18,9 +18,9 @@
 #include "base/test/mock_callback.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h"
 #include "chrome/browser/apps/app_shim/app_shim_host_mac.h"
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/avatar_menu.h"
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
 #include "chrome/common/mac/app_shim.mojom.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/testing_pref_service.h"
diff --git a/chrome/browser/apps/platform_apps/shortcut_manager.cc b/chrome/browser/apps/platform_apps/shortcut_manager.cc
index 2dba9a3a..7cb07206 100644
--- a/chrome/browser/apps/platform_apps/shortcut_manager.cc
+++ b/chrome/browser/apps/platform_apps/shortcut_manager.cc
@@ -30,7 +30,6 @@
 #include "extensions/common/extension_set.h"
 
 #if defined(OS_MACOSX)
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
 #include "chrome/common/mac/app_mode_common.h"
 #endif
 
@@ -38,15 +37,6 @@
 
 namespace {
 
-#if defined(OS_MACOSX)
-bool UseAppShimRegistry(content::BrowserContext* browser_context,
-                        const Extension* extension) {
-  if (browser_context->IsOffTheRecord())
-    return false;
-  return extension->is_app() && extension->from_bookmark();
-}
-#endif
-
 // This version number is stored in local prefs to check whether app shortcuts
 // need to be recreated. This might happen when we change various aspects of app
 // shortcuts like command-line flags or associated icons, binaries, etc.
@@ -114,23 +104,6 @@
   }
 }
 
-void AppShortcutManager::OnExtensionLoaded(
-    content::BrowserContext* browser_context,
-    const Extension* extension) {
-#if defined(OS_MACOSX)
-  // Register installed apps as soon as their extension is loaded. This happens
-  // when the profile is loaded. This is redundant, because apps are registered
-  // when they are installed. It is necessary, however, because app registration
-  // was added long after app installation launched. This should be removed
-  // after shipping for a few versions (whereupon it may be assumed that most
-  // applications have been registered).
-  if (UseAppShimRegistry(browser_context, extension)) {
-    AppShimRegistry::Get()->OnAppInstalledForProfile(extension->id(),
-                                                     profile_->GetPath());
-  }
-#endif
-}
-
 void AppShortcutManager::OnExtensionWillBeInstalled(
     content::BrowserContext* browser_context,
     const Extension* extension,
@@ -139,13 +112,6 @@
   if (!extension->is_app())
     return;
 
-#if defined(OS_MACOSX)
-  if (UseAppShimRegistry(browser_context, extension)) {
-    AppShimRegistry::Get()->OnAppInstalledForProfile(extension->id(),
-                                                     profile_->GetPath());
-  }
-#endif
-
   // If the app is being updated, update any existing shortcuts but do not
   // create new ones. If it is being installed, automatically create a
   // shortcut in the applications menu (e.g., Start Menu).
@@ -161,21 +127,6 @@
     content::BrowserContext* browser_context,
     const Extension* extension,
     extensions::UninstallReason reason) {
-#if defined(OS_MACOSX)
-  // TODO(crbug.com/860581): Move this code to BookmarkAppShortcutManager.
-  if (UseAppShimRegistry(browser_context, extension)) {
-    bool delete_multi_profile_shortcuts =
-        AppShimRegistry::Get()->OnAppUninstalledForProfile(extension->id(),
-                                                           profile_->GetPath());
-    if (delete_multi_profile_shortcuts) {
-      web_app::internals::GetShortcutIOTaskRunner()->PostTask(
-          FROM_HERE,
-          base::BindOnce(&web_app::internals::DeleteMultiProfileShortcutsForApp,
-                         extension->id()));
-    }
-  }
-#endif
-
   // Bookmark apps are handled in
   // web_app::AppShortcutManager::OnWebAppWillBeUninstalled()
   if (!extension->from_bookmark())
@@ -186,24 +137,6 @@
     const base::FilePath& profile_path) {
   if (profile_path != profile_->GetPath())
     return;
-
-#if defined(OS_MACOSX)
-  // If any multi-profile app shims exist only for this profile, delete them.
-  std::set<std::string> apps_for_profile =
-      AppShimRegistry::Get()->GetInstalledAppsForProfile(profile_path);
-  for (const auto& app_id : apps_for_profile) {
-    bool delete_multi_profile_shortcuts =
-        AppShimRegistry::Get()->OnAppUninstalledForProfile(app_id,
-                                                           profile_path);
-    if (delete_multi_profile_shortcuts) {
-      web_app::internals::GetShortcutIOTaskRunner()->PostTask(
-          FROM_HERE,
-          base::BindOnce(&web_app::internals::DeleteMultiProfileShortcutsForApp,
-                         app_id));
-    }
-  }
-#endif
-
   web_app::internals::GetShortcutIOTaskRunner()->PostTask(
       FROM_HERE,
       base::BindOnce(&web_app::internals::DeleteAllShortcutsForProfile,
diff --git a/chrome/browser/apps/platform_apps/shortcut_manager.h b/chrome/browser/apps/platform_apps/shortcut_manager.h
index 1f4aa1a..02e8a26 100644
--- a/chrome/browser/apps/platform_apps/shortcut_manager.h
+++ b/chrome/browser/apps/platform_apps/shortcut_manager.h
@@ -40,8 +40,6 @@
   void UpdateShortcutsForAllAppsIfNeeded();
 
   // extensions::ExtensionRegistryObserver.
-  void OnExtensionLoaded(content::BrowserContext* browser_context,
-                         const extensions::Extension* extension) override;
   void OnExtensionWillBeInstalled(content::BrowserContext* browser_context,
                                   const extensions::Extension* extension,
                                   bool is_update,
diff --git a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
index 6d0aea6b..899a675f 100644
--- a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
+++ b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
@@ -377,7 +377,7 @@
   std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
 };
 
-IN_PROC_BROWSER_TEST_F(WebBluetoothTest, DISABLED_WebBluetoothAfterCrash) {
+IN_PROC_BROWSER_TEST_F(WebBluetoothTest, WebBluetoothAfterCrash) {
   // Make sure we can use Web Bluetooth after the tab crashes.
   // Set up adapter with one device.
   adapter_->SetIsPresent(false);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index b77c6fff..507294c6 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1272,6 +1272,10 @@
     "input_method/input_method_syncer.h",
     "input_method/native_input_method_engine.cc",
     "input_method/native_input_method_engine.h",
+    "input_method/suggestion_window_controller.cc",
+    "input_method/suggestion_window_controller.h",
+    "input_method/suggestion_window_controller_impl.cc",
+    "input_method/suggestion_window_controller_impl.h",
     "kerberos/kerberos_credentials_manager.cc",
     "kerberos/kerberos_credentials_manager.h",
     "kerberos/kerberos_credentials_manager_factory.cc",
@@ -2682,6 +2686,7 @@
     "android_sms/pairing_lost_notifier_unittest.cc",
     "app_mode/app_session_unittest.cc",
     "app_mode/startup_app_launcher_unittest.cc",
+    "app_mode/web_app/web_kiosk_app_launcher_unittest.cc",
     "apps/apk_web_app_installer_unittest.cc",
     "apps/intent_helper/chromeos_apps_navigation_throttle_unittest.cc",
     "apps/metrics/intent_handling_metrics_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/speech_monitor.cc b/chrome/browser/chromeos/accessibility/speech_monitor.cc
index ac0ce3b..566d4f8 100644
--- a/chrome/browser/chromeos/accessibility/speech_monitor.cc
+++ b/chrome/browser/chromeos/accessibility/speech_monitor.cc
@@ -230,6 +230,10 @@
 }
 
 void SpeechMonitor::MaybeContinueReplay() {
+  // This method can be called prior to Replay() being called.
+  if (!replay_called_)
+    return;
+
   auto it = replay_queue_.begin();
   while (it != replay_queue_.end()) {
     if (it->first()) {
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
index 0ff8db8..055e0effa 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -140,34 +140,32 @@
 
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
 
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
+  sm_.ExpectSpeech("Shelf");
 
   // Press space on the launcher button in shelf, this opens peeking launcher.
-  SendKeyPressWithSearch(ui::VKEY_SPACE);
-  while (sm_.GetNextUtterance() != "Launcher, partial view") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("Launcher, partial view");
 
   // Send a key press to enable keyboard traversal
-  SendKeyPressWithSearchAndShift(ui::VKEY_TAB);
+  sm_.Call([this]() { SendKeyPressWithSearchAndShift(ui::VKEY_TAB); });
 
   // Move focus to expand all apps button.
-  SendKeyPressWithSearchAndShift(ui::VKEY_TAB);
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearchAndShift(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Expand to all apps");
 
   // Press space on expand arrow to go to fullscreen launcher.
-  SendKeyPressWithSearch(ui::VKEY_SPACE);
-  while (sm_.GetNextUtterance() != "Launcher, all apps") {
-  }
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech("Launcher, all apps");
 
   // Make sure the first traversal left is not the expand arrow button.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_NE("Expand to all apps", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectNextSpeechIsNot("Expand to all apps");
 
   // Make sure the second traversal left is not the expand arrow button.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_NE("Expand to all apps", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectNextSpeechIsNot("Expand to all apps");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest,
@@ -256,61 +254,49 @@
   sm_.Replay();
 }
 
-// TODO(newcomer): reimplement this test once the AppListFocus changes are
-// complete (http://crbug.com/784942).
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest,
-                       DISABLED_NavigateAppLauncher) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest, NavigateAppLauncher) {
   EnableChromeVox();
 
+  // Add one app to the applist.
+  PopulateApps(1);
+
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
 
   // Wait for it to say "Launcher", "Button", "Shelf", "Tool bar".
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Shelf", sm_.GetNextUtterance());
-  EXPECT_EQ("Tool bar", sm_.GetNextUtterance());
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
 
   // Click on the launcher, it brings up the app list UI.
-  SendKeyPress(ui::VKEY_SPACE);
-  while ("Search or type URL" != sm_.GetNextUtterance()) {
-  }
-  while ("Edit text" != sm_.GetNextUtterance()) {
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech(
+      "Search your device, apps, and web. Use the arrow keys to navigate your "
+      "apps.");
+  sm_.ExpectSpeech("Edit text");
 
   // Close it and open it again.
-  SendKeyPress(ui::VKEY_ESCAPE);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "*window*"))
-      break;
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_ESCAPE); });
+  sm_.ExpectSpeechPattern("*window*");
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Button"))
-      break;
-  }
-  SendKeyPress(ui::VKEY_SPACE);
+  sm_.Call(
+      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  sm_.ExpectSpeechPattern("Launcher");
+  sm_.ExpectSpeech("Button");
 
-  // Now type a space into the text field and wait until we hear "space".
-  // This makes the test more robust as it allows us to skip over other
-  // speech along the way.
-  SendKeyPress(ui::VKEY_SPACE);
-  while (true) {
-    if ("space" == sm_.GetNextUtterance())
-      break;
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_SPACE); });
+  sm_.ExpectSpeech(
+      "Search your device, apps, and web. Use the arrow keys to navigate your "
+      "apps.");
 
-  // Now press the down arrow and we should be focused on an app button
+  // Now press the right arrow and we should be focused on an app button
   // in a dialog.
-  SendKeyPress(ui::VKEY_DOWN);
-  while ("Button" != sm_.GetNextUtterance()) {
-  }
+  // THis doesn't work though (to be done below).
+
+  // TODO(newcomer): reimplement this test once the AppListFocus changes are
+  // complete (http://crbug.com/784942).
+
+  sm_.Replay();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index 759ce77..37294b2 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -89,6 +89,11 @@
       nullptr, key, true, false, false, false)));
 }
 
+void LoggedInSpokenFeedbackTest::SendKeyPressWithShift(ui::KeyboardCode key) {
+  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+      nullptr, key, false, true, false, false)));
+}
+
 void LoggedInSpokenFeedbackTest::SendKeyPressWithSearchAndShift(
     ui::KeyboardCode key) {
   ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
@@ -174,85 +179,75 @@
   }
 }
 
-// This test is very flakey with ChromeVox Next since we generate a lot more
-// utterances for text fields.
-// TODO(dtseng): Fix properly.
-IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, DISABLED_AddBookmark) {
+IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, AddBookmark) {
   EnableChromeVox();
   chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR);
 
   // Create a bookmark with title "foo".
   chrome::ExecuteCommand(browser(), IDC_BOOKMARK_THIS_TAB);
-  EXPECT_EQ("Bookmark added! dialog Bookmark name about:blank Edit text",
-            sm_.GetNextUtterance());
-  EXPECT_EQ("about:blank", sm_.GetNextUtterance());
 
-  SendKeyPress(ui::VKEY_F);
-  EXPECT_EQ("f", sm_.GetNextUtterance());
-  SendKeyPress(ui::VKEY_O);
-  EXPECT_EQ("o", sm_.GetNextUtterance());
-  SendKeyPress(ui::VKEY_O);
-  EXPECT_EQ("o", sm_.GetNextUtterance());
+  sm_.ExpectSpeech("Bookmark name");
+  sm_.ExpectSpeech("about:blank");
+  sm_.ExpectSpeech("selected");
+  sm_.ExpectSpeech("Edit text");
+  sm_.ExpectSpeech("Bookmark added");
+  sm_.ExpectSpeech("Dialog");
+  sm_.ExpectSpeech("Bookmark added, window");
 
-  SendKeyPress(ui::VKEY_TAB);
-  EXPECT_EQ("Bookmark folder combo Box Bookmarks bar", sm_.GetNextUtterance());
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_F);
+    SendKeyPress(ui::VKEY_O);
+    SendKeyPress(ui::VKEY_O);
+  });
+  sm_.ExpectSpeech("F");
+  sm_.ExpectSpeech("O");
+  sm_.ExpectSpeech("O");
 
-  SendKeyPress(ui::VKEY_RETURN);
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Bookmark folder");
+  sm_.ExpectSpeech("Bookmarks bar");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("has pop up");
 
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*oolbar*"));
-  // Wait for active window change to be announced to avoid interference from
-  // that below.
-  while (sm_.GetNextUtterance() != "window about blank tab") {
-    // Do nothing.
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("More…");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Remove");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech("Done");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_RETURN); });
+  // Focus goes back to window.
+  sm_.ExpectSpeechPattern("about:blank*");
 
   // Focus bookmarks bar and listen for "foo".
-  chrome::ExecuteCommand(browser(), IDC_FOCUS_BOOKMARKS);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    VLOG(0) << "Got utterance: " << utterance;
-    if (utterance == "Bookmarks,")
-      break;
-  }
-  EXPECT_EQ("foo,", sm_.GetNextUtterance());
-  EXPECT_EQ("button", sm_.GetNextUtterance());
+  sm_.Call(
+      [this]() { chrome::ExecuteCommand(browser(), IDC_FOCUS_BOOKMARKS); });
+  sm_.ExpectSpeech("foo");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Bookmarks");
+  sm_.ExpectSpeech("Tool bar");
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest,
-                       DISABLED_NavigateNotificationCenter) {
+IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, NavigateNotificationCenter) {
   EnableChromeVox();
 
   EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_MESSAGE_CENTER_BUBBLE));
+  sm_.ExpectSpeech(
+      "Quick Settings, Press search plus left to access the notification "
+      "center., window");
 
-  // Tab to request the initial focus.
-  SendKeyPress(ui::VKEY_TAB);
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  // If you are hitting this in the course of changing the UI, please fix. This
+  // item needs a label.
+  sm_.ExpectSpeech("List item");
 
-  // Wait for it to say "Notification Center, window".
-  while ("Notification Center, window" != sm_.GetNextUtterance()) {
-  }
+  // Furthermore, navigation is generally broken using Search+Left.
 
-  // Tab until we get to the Do Not Disturb button.
-  SendKeyPress(ui::VKEY_TAB);
-  do {
-    std::string ut = sm_.GetNextUtterance();
-
-    if (ut == "Do not disturb")
-      break;
-    else if (ut == "Button")
-      SendKeyPress(ui::VKEY_TAB);
-  } while (true);
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Not pressed", sm_.GetNextUtterance());
-
-  SendKeyPress(ui::VKEY_SPACE);
-  EXPECT_EQ("Do not disturb", sm_.GetNextUtterance());
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Pressed", sm_.GetNextUtterance());
-
-  SendKeyPress(ui::VKEY_SPACE);
-  EXPECT_EQ("Do not disturb", sm_.GetNextUtterance());
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
-  EXPECT_EQ("Not pressed", sm_.GetNextUtterance());
+  sm_.Replay();
 }
 
 //
@@ -285,9 +280,7 @@
                          ::testing::Values(kTestAsNormalUser,
                                            kTestAsGuestUser));
 
-// TODO(tommi): Flakily hitting HasOneRef DCHECK in
-// AudioOutputResampler::Shutdown, see crbug.com/630031.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_EnableSpokenFeedback) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, EnableSpokenFeedback) {
   EnableChromeVox();
 }
 
@@ -299,21 +292,37 @@
   EXPECT_EQ("Button", sm_.GetNextUtterance());
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_TypeInOmnibox) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TypeInOmnibox) {
   EnableChromeVox();
 
-  // Location bar has focus by default so just start typing.
-  SendKeyPress(ui::VKEY_X);
-  EXPECT_EQ("x", sm_.GetNextUtterance());
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;charset=utf-8,<p>unused</p>"));
 
-  SendKeyPress(ui::VKEY_Y);
-  EXPECT_EQ("y", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithControl(ui::VKEY_L); });
+  sm_.ExpectSpeech("Address and search bar");
 
-  SendKeyPress(ui::VKEY_Z);
-  EXPECT_EQ("z", sm_.GetNextUtterance());
+  sm_.Call([this]() {
+    // Select all the text.
+    SendKeyPressWithControl(ui::VKEY_A);
 
-  SendKeyPress(ui::VKEY_BACK);
-  EXPECT_EQ("z", sm_.GetNextUtterance());
+    // Type x, y, and z.
+    SendKeyPress(ui::VKEY_X);
+    SendKeyPress(ui::VKEY_Y);
+    SendKeyPress(ui::VKEY_Z);
+  });
+  sm_.ExpectSpeech("X");
+  sm_.ExpectSpeech("Y");
+  sm_.ExpectSpeech("Z");
+
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_BACK); });
+  sm_.ExpectSpeech("Z");
+
+  // Auto completions.
+  sm_.ExpectSpeech("xy search");
+  sm_.ExpectSpeech("List item");
+  sm_.ExpectSpeech("1 of 1");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, FocusShelf) {
@@ -339,9 +348,7 @@
 
 // Verifies that pressing right arrow button with search button should move
 // focus to the next ShelfItem instead of the last one
-// (see https://crbug.com/947683).
-// This test is flaky, see http://crbug.com/997628
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ShelfIconFocusForward) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ShelfIconFocusForward) {
   const std::string title("MockApp");
   ChromeLauncherController* controller = ChromeLauncherController::instance();
 
@@ -353,41 +360,31 @@
       ash::ShelfID("FakeApp"), controller->shelf_model()->item_count(),
       base::ASCIIToUTF16(title));
 
-  // Wait for the change on ShelfModel to reach ash.
-  base::RunLoop().RunUntilIdle();
-
   // Focus on the shelf.
-  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-
-  ASSERT_EQ("Button", sm_.GetNextUtterance());
-  ASSERT_EQ("Shelf", sm_.GetNextUtterance());
-  ASSERT_EQ("Tool bar", sm_.GetNextUtterance());
-  ASSERT_EQ(", window", sm_.GetNextUtterance());
-  ASSERT_EQ("Press Search plus Space to activate", sm_.GetNextUtterance());
+  sm_.Call([this]() { PerformAcceleratorAction(ash::FOCUS_SHELF); });
+  sm_.ExpectSpeech("Launcher");
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
 
   // Verifies that pressing right key with search key should move the focus of
   // ShelfItem correctly.
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), title));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  // Chromium or Google Chrome button here (not being tested).
+  sm_.ExpectSpeech("Button");
+  sm_.ExpectSpeech("Shelf");
+  sm_.ExpectSpeech("Tool bar");
+
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_RIGHT); });
+  sm_.ExpectSpeech("MockApp");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 // Verifies that speaking text under mouse works for Shelf button and voice
 // announcements should not be stacked when mouse goes over many Shelf buttons
-// (see https://crbug.com/958120 and https://crbug.com/921182).
-// TODO(crbug.com/921182): Fix test correctness/reliability and re-enable.
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest,
-                       DISABLED_SpeakingTextUnderMouseForShelfItem) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, SpeakingTextUnderMouseForShelfItem) {
   // Add the ShelfItem to the ShelfModel after enabling the ChromeVox. Because
   // when an extension is enabled, the ShelfItems which are not recorded as
   // pinned apps in user preference will be removed.
@@ -405,7 +402,6 @@
     controller->CreateAppShortcutLauncherItem(
         ash::ShelfID(app_id), base_index + i, base::ASCIIToUTF16(app_title));
   }
-  base::RunLoop().RunUntilIdle();
 
   // Enable the function of speaking text under mouse.
   ash::EventRewriterController::Get()->SetSendMouseEventsToDelegate(true);
@@ -413,45 +409,26 @@
   // Focus on the Shelf because voice text for focusing on Shelf is fixed. Wait
   // until voice announcements are finished.
   EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Launcher"))
-      break;
-  }
-  ASSERT_EQ("Button", sm_.GetNextUtterance());
-  ASSERT_EQ("Shelf", sm_.GetNextUtterance());
-  ASSERT_EQ("Tool bar", sm_.GetNextUtterance());
-  ASSERT_EQ(", window", sm_.GetNextUtterance());
-  ASSERT_EQ("Press Search plus Space to activate", sm_.GetNextUtterance());
+  sm_.ExpectSpeechPattern("Launcher");
 
   // Hover mouse on the Shelf button. Verifies that text under mouse is spoken.
-  ash::ShelfView* shelf_view =
-      ash::Shelf::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow())
-          ->shelf_widget()
-          ->shelf_view_for_testing();
-  const int first_app_index =
-      shelf_view->model()->GetItemIndexForType(ash::TYPE_PINNED_APP);
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(first_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "MockApp0"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() {
+    ash::ShelfView* shelf_view =
+        ash::Shelf::ForWindow(ash::Shell::Get()->GetPrimaryRootWindow())
+            ->shelf_widget()
+            ->shelf_view_for_testing();
+    const int first_app_index =
+        shelf_view->model()->GetItemIndexForType(ash::TYPE_PINNED_APP);
+    SendMouseMoveTo(shelf_view->view_model()
+                        ->view_at(first_app_index)
+                        ->GetBoundsInScreen()
+                        .CenterPoint());
+  });
 
-  // Move mouse to the third Shelf button through the second one. Verifies that
-  // only the last Shelf button is announced by ChromeVox.
-  const int second_app_index = first_app_index + 1;
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(second_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  const int third_app_index = first_app_index + 2;
-  SendMouseMoveTo(shelf_view->view_model()
-                      ->view_at(third_app_index)
-                      ->GetBoundsInScreen()
-                      .CenterPoint());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "MockApp2"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.ExpectSpeechPattern("MockApp*");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OpenStatusTray) {
@@ -470,88 +447,54 @@
 
 // Fails on ASAN. See http://crbug.com/776308 . (Note MAYBE_ doesn't work well
 // with parameterized tests).
-#if !defined(ADDRESS_SANITIZER) && !defined(OS_CHROMEOS)
+#if !defined(ADDRESS_SANITIZER)
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, NavigateSystemTray) {
   EnableChromeVox();
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Status tray,"))
-      break;
-  }
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "*window"))
-      break;
-  }
+  sm_.Call(
+      [this]() { (PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE)); });
+  sm_.ExpectSpeechPattern(
+      "Quick Settings, Press search plus left to access the notification "
+      "center., window");
 
-  SendKeyPress(ui::VKEY_TAB);
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Button"))
-      break;
-  }
-
-  // Next element.
-  SendKeyPressWithSearch(ui::VKEY_RIGHT);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
+  sm_.ExpectSpeech(GetParam() == kTestAsGuestUser ? "Exit guest" : "Sign out");
+  sm_.ExpectSpeech("Button");
 
   // Next button.
-  SendKeyPressWithSearch(ui::VKEY_B);
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_B); });
+  sm_.ExpectSpeech("Shut down");
+  sm_.ExpectSpeech("Button");
 
-  // Navigate to Bluetooth sub-menu and open it.
-  while (true) {
-    SendKeyPress(ui::VKEY_TAB);
-    std::string content = sm_.GetNextUtterance();
-    std::string role = sm_.GetNextUtterance();
-    if (base::MatchPattern(content, "*Bluetooth*") &&
-        base::MatchPattern(role, "Button"))
-      break;
-  }
-  SendKeyPress(ui::VKEY_RETURN);
-
-  // Navigate to return to previous menu button and press it.
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Previous menu"))
-      break;
-    SendKeyPress(ui::VKEY_TAB);
-  }
-  SendKeyPress(ui::VKEY_RETURN);
-
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (base::MatchPattern(utterance, "Bluetooth*"))
-      break;
-  }
+  sm_.Replay();
 }
-#endif  // !defined(ADDRESS_SANITIZER) && !defined(OS_CHROMEOS)
+#endif  // !defined(ADDRESS_SANITIZER)
 
-// See http://crbug.com/443608
+// TODO: these brightness announcements are actually not made.
+// https://crbug.com/1064788
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ScreenBrightness) {
   EnableChromeVox();
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::BRIGHTNESS_UP));
-  EXPECT_TRUE(
-      base::MatchPattern(sm_.GetNextUtterance(), "Brightness * percent"));
+  sm_.Call([this]() { (PerformAcceleratorAction(ash::BRIGHTNESS_UP)); });
+  sm_.ExpectSpeechPattern("Brightness * percent");
 
-  EXPECT_TRUE(PerformAcceleratorAction(ash::BRIGHTNESS_DOWN));
-  EXPECT_TRUE(
-      base::MatchPattern(sm_.GetNextUtterance(), "Brightness * percent"));
+  sm_.Call([this]() { (PerformAcceleratorAction(ash::BRIGHTNESS_DOWN)); });
+  sm_.ExpectSpeechPattern("Brightness * percent");
+
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_VolumeSlider) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, VolumeSlider) {
   EnableChromeVox();
 
-  // Volume slider does not fire valueChanged event on first key press because
-  // it has no widget.
-  EXPECT_TRUE(PerformAcceleratorAction(ash::VOLUME_UP));
-  EXPECT_TRUE(PerformAcceleratorAction(ash::VOLUME_UP));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "* percent*"));
+  sm_.Call([this]() {
+    // Volume slider does not fire valueChanged event on first key press because
+    // it has no widget.
+    PerformAcceleratorAction(ash::VOLUME_UP);
+    PerformAcceleratorAction(ash::VOLUME_UP);
+  });
+  sm_.ExpectSpeechPattern("* percent*");
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OverviewMode) {
@@ -576,129 +519,108 @@
   }
 }
 
-#if defined(MEMORY_SANITIZER) || defined(OS_CHROMEOS)
-// Fails under MemorySanitizer: http://crbug.com/472125
-// Test is flaky under ChromeOS: http://crbug.com/897249
-#define MAYBE_ChromeVoxShiftSearch DISABLED_ChromeVoxShiftSearch
-#else
-#define MAYBE_ChromeVoxShiftSearch ChromeVoxShiftSearch
-#endif
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, MAYBE_ChromeVoxShiftSearch) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxFindInPage) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(
       browser(),
       GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Click me")
-      break;
-  }
+
+  sm_.ExpectSpeech("Click me");
 
   // Press Search+/ to enter ChromeVox's "find in page".
   SendKeyPressWithSearch(ui::VKEY_OEM_2);
-
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Find in page")
-      break;
-  }
+  sm_.ExpectSpeech("Find in page");
+  sm_.Replay();
 }
 
-#if defined(MEMORY_SANITIZER) || defined(OS_CHROMEOS)
-// Fails under MemorySanitizer: http://crbug.com/472125
-// TODO(crbug.com/721475): Flaky on CrOS.
-#define MAYBE_ChromeVoxNavigateAndSelect DISABLED_ChromeVoxNavigateAndSelect
-#else
-#define MAYBE_ChromeVoxNavigateAndSelect ChromeVoxNavigateAndSelect
-#endif
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, MAYBE_ChromeVoxNavigateAndSelect) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNavigateAndSelect) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(browser(),
                                GURL("data:text/html;charset=utf-8,"
                                     "<h1>Title</h1>"
                                     "<button autofocus>Click me</button>"));
-  while (true) {
-    std::string utterance = sm_.GetNextUtterance();
-    if (utterance == "Click me")
-      break;
-  }
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
+
+  sm_.ExpectSpeech("Click me");
 
   // Press Search+Left to navigate to the previous item.
-  SendKeyPressWithSearch(ui::VKEY_LEFT);
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
-  EXPECT_EQ("Heading 1", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_LEFT); });
+  sm_.ExpectSpeech("Title");
+  sm_.ExpectSpeech("Heading 1");
 
   // Press Search+S to select the text.
-  SendKeyPressWithSearch(ui::VKEY_S);
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
-  EXPECT_EQ("selected", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_S); });
+  sm_.ExpectSpeech("Title");
+  sm_.ExpectSpeech("selected");
 
   // Press again to end the selection.
-  SendKeyPressWithSearch(ui::VKEY_S);
-  EXPECT_EQ("End selection", sm_.GetNextUtterance());
-  EXPECT_EQ("Title", sm_.GetNextUtterance());
+  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_S); });
+  sm_.ExpectSpeech("End selection");
+
+  sm_.Replay();
 }
 
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ChromeVoxNextStickyMode) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxStickyMode) {
   EnableChromeVox();
 
   ui_test_utils::NavigateToURL(
       browser(),
       GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
-  while ("Button" != sm_.GetNextUtterance()) {
-  }
+
+  sm_.ExpectSpeech("Click me");
 
   // Press the sticky-key sequence: Search Search.
-  SendKeyPress(ui::VKEY_LWIN);
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_LWIN);
 
-  // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
-  // it.
-  base::PostDelayedTask(
-      FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
-                     base::Unretained(this), ui::VKEY_LWIN),
-      base::TimeDelta::FromMilliseconds(200));
+    // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
+    // it.
+    base::PostDelayedTask(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
+                       base::Unretained(this), ui::VKEY_LWIN),
+        base::TimeDelta::FromMilliseconds(200));
+  });
 
-  EXPECT_EQ("Sticky mode enabled", sm_.GetNextUtterance());
+  sm_.ExpectSpeech("Sticky mode enabled");
 
-  SendKeyPress(ui::VKEY_H);
-  while ("No next heading" != sm_.GetNextUtterance()) {
-  }
+  sm_.Call([this]() { SendKeyPress(ui::VKEY_H); });
+  sm_.ExpectSpeech("No next heading");
 
-  SendKeyPress(ui::VKEY_LWIN);
+  sm_.Call([this]() {
+    SendKeyPress(ui::VKEY_LWIN);
 
-  // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
-  // it.
-  base::PostDelayedTask(
-      FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
-                     base::Unretained(this), ui::VKEY_LWIN),
-      base::TimeDelta::FromMilliseconds(200));
+    // Sticky key has a minimum 100 ms check to prevent key repeat from toggling
+    // it.
+    base::PostDelayedTask(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindOnce(&LoggedInSpokenFeedbackTest::SendKeyPress,
+                       base::Unretained(this), ui::VKEY_LWIN),
+        base::TimeDelta::FromMilliseconds(200));
+  });
+  sm_.ExpectSpeech("Sticky mode disabled");
 
-  while ("Sticky mode disabled" != sm_.GetNextUtterance()) {
-  }
+  sm_.Replay();
 }
 
-// Flaky on Linux ChromiumOS MSan Tests. https://crbug.com/752427
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_TouchExploreStatusTray) {
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TouchExploreStatusTray) {
   EnableChromeVox();
   SimulateTouchScreenInChromeVox();
 
   // Send an accessibility hover event on the system tray, which is
   // what we get when you tap it on a touch screen when ChromeVox is on.
-  ash::TrayBackgroundView* tray = ash::Shell::Get()
-                                      ->GetPrimaryRootWindowController()
-                                      ->GetStatusAreaWidget()
-                                      ->unified_system_tray();
-  tray->NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
+  sm_.Call([]() {
+    ash::TrayBackgroundView* tray = ash::Shell::Get()
+                                        ->GetPrimaryRootWindowController()
+                                        ->GetStatusAreaWidget()
+                                        ->unified_system_tray();
+    tray->NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
+  });
+  sm_.ExpectSpeechPattern("Status tray, time* Battery at* percent*");
+  sm_.ExpectSpeech("Button");
 
-  EXPECT_EQ("Status tray,", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "time*,"));
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Battery*"));
-  EXPECT_EQ("Button", sm_.GetNextUtterance());
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNextTabRecovery) {
@@ -792,66 +714,22 @@
   DISALLOW_COPY_AND_ASSIGN(OobeSpokenFeedbackTest);
 };
 
-// Test is flaky: http://crbug.com/346797
-IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, DISABLED_SpokenFeedbackInOobe) {
+IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, SpokenFeedbackInOobe) {
   ui_controls::EnableUIControls();
   ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
-
-  LoginDisplayHost* login_display_host = LoginDisplayHost::default_host();
-  WebUILoginView* web_ui_login_view = login_display_host->GetWebUILoginView();
-  views::Widget* widget = web_ui_login_view->GetWidget();
-  gfx::NativeWindow window = widget->GetNativeWindow();
-
-  // We expect to be in the language select dropdown for this test to work,
-  // so make sure that's the case.
-  test::OobeJS().ExecuteAsync("$('language-select').focus()");
   AccessibilityManager::Get()->EnableSpokenFeedback(true);
-  ASSERT_TRUE(sm_.SkipChromeVoxEnabledMessage());
-  // There's no guarantee that ChromeVox speaks anything when injected after
-  // the page loads, which is by design.  Tab forward and then backward
-  // to make sure we get the right feedback from the language and keyboard
-  // selection fields.
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      window, ui::VKEY_TAB, false, false, false, false));
 
-  while (sm_.GetNextUtterance() != "Select your keyboard:") {
-  }
-  EXPECT_EQ("U S", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Combo box * of *"));
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      window, ui::VKEY_TAB, false, true /*shift*/, false, false));
-  while (sm_.GetNextUtterance() != "Select your language:") {
-  }
-  EXPECT_EQ("English ( United States)", sm_.GetNextUtterance());
-  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Combo box * of *"));
-}
+  // The Let's go button gets initial focus.
+  sm_.ExpectSpeech("Let's go");
 
-// This test is flaky (https://crbug.com/1013551).
-IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest,
-                       DISABLED_ChromeVoxPanelTabsMenuEmpty) {
-  // The ChromeVox panel should not populate the tabs menu if we are in the
-  // OOBE.
-  ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
-  AccessibilityManager::Get()->EnableSpokenFeedback(true);
-  // Included to reduce flakiness.
-  while (sm_.GetNextUtterance() != "Press Search plus Space to activate") {
-  }
-  // Press [search + .] to open ChromeVox Panel
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_OEM_PERIOD, false, false, false, true));
-  while (sm_.GetNextUtterance() != "ChromeVox Panel") {
-  }
-  // Go to tabs menu and verify that it has no items.
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_RIGHT, false, false, false, false));
-  while (sm_.GetNextUtterance() != "Speech") {
-  }
-  ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-      nullptr, ui::VKEY_RIGHT, false, false, false, false));
-  while (sm_.GetNextUtterance() != "Tabs") {
-  }
-  EXPECT_EQ("Menu", sm_.GetNextUtterance());
-  EXPECT_EQ("No items", sm_.GetNextUtterance());
+  sm_.Call([]() {
+    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+        nullptr, ui::VKEY_TAB, false, false, false, false));
+  });
+  sm_.ExpectSpeech("Shut down");
+  sm_.ExpectSpeech("Button");
+
+  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest,
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
index b20d15e..aa6be934 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h
@@ -29,6 +29,7 @@
   // Simulate key press event.
   void SendKeyPress(ui::KeyboardCode key);
   void SendKeyPressWithControl(ui::KeyboardCode key);
+  void SendKeyPressWithShift(ui::KeyboardCode key);
   void SendKeyPressWithSearchAndShift(ui::KeyboardCode key);
   void SendKeyPressWithSearch(ui::KeyboardCode key);
   void SendKeyPressWithSearchAndControl(ui::KeyboardCode key);
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.cc b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.cc
index 2398f2d..16003ec5 100644
--- a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.cc
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.cc
@@ -30,7 +30,9 @@
     WebKioskAppLauncher::Delegate* delegate)
     : profile_(profile),
       delegate_(delegate),
-      url_loader_(std::make_unique<web_app::WebAppUrlLoader>()) {}
+      url_loader_(std::make_unique<web_app::WebAppUrlLoader>()),
+      data_retriever_factory_(base::BindRepeating(
+          &std::make_unique<web_app::WebAppDataRetriever>)) {}
 
 WebKioskAppLauncher::~WebKioskAppLauncher() = default;
 
@@ -52,9 +54,8 @@
   DCHECK(!is_installed_);
   install_task_.reset(new web_app::WebAppInstallTask(
       profile_, /*registrar=*/nullptr, /*shortcut_manager=*/nullptr,
-      /*file_handler_manager=*/nullptr,
-      /*install_finalizer=*/nullptr,
-      std::make_unique<web_app::WebAppDataRetriever>()));
+      /*file_handler_manager=*/nullptr, /*install_finalizer=*/nullptr,
+      data_retriever_factory_.Run()));
   install_task_->LoadAndRetrieveWebApplicationInfoWithIcons(
       WebKioskAppManager::Get()->GetAppByAccountId(account_id_)->install_url(),
       url_loader_.get(),
@@ -99,7 +100,11 @@
                  ? app->launch_url()
                  : app->install_url();
 
-  Browser::CreateParams params(Browser::TYPE_APP, profile_, false);
+  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
+      app->name(), true, gfx::Rect(), profile_, false);
+  if (test_browser_window_) {
+    params.window = test_browser_window_;
+  }
 
   browser_ = Browser::Create(params);
   NavigateParams nav_params(browser_, url,
@@ -121,4 +126,19 @@
   install_task_.reset();
 }
 
+void WebKioskAppLauncher::SetDataRetrieverFactoryForTesting(
+    base::RepeatingCallback<std::unique_ptr<web_app::WebAppDataRetriever>()>
+        data_retriever_factory) {
+  data_retriever_factory_ = data_retriever_factory;
+}
+
+void WebKioskAppLauncher::SetBrowserWindowForTesting(BrowserWindow* window) {
+  test_browser_window_ = window;
+}
+
+void WebKioskAppLauncher::SetUrlLoaderForTesting(
+    std::unique_ptr<web_app::WebAppUrlLoader> url_loader) {
+  url_loader_ = std::move(url_loader);
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.h b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.h
index c09062f..161fe36 100644
--- a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.h
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
@@ -18,11 +19,13 @@
 #include "url/gurl.h"
 
 class Browser;
+class BrowserWindow;
 class Profile;
 
 namespace web_app {
 class WebAppInstallTask;
 class WebAppUrlLoader;
+class WebAppDataRetriever;
 }  // namespace web_app
 
 namespace chromeos {
@@ -58,6 +61,18 @@
   // Stops current installation.
   virtual void CancelCurrentInstallation();
 
+  // Replaces data retriever used for new WebAppInstallTask in tests.
+  void SetDataRetrieverFactoryForTesting(
+      base::RepeatingCallback<std::unique_ptr<web_app::WebAppDataRetriever>()>
+          data_retriever_factory);
+
+  // Replaces default browser window with |window| during launch.
+  void SetBrowserWindowForTesting(BrowserWindow* window);
+
+  // Replaces current |url_loader_| with one provided.
+  void SetUrlLoaderForTesting(
+      std::unique_ptr<web_app::WebAppUrlLoader> url_loader);
+
  private:
   void OnAppDataObtained(std::unique_ptr<WebApplicationInfo> app_info);
 
@@ -75,6 +90,12 @@
   std::unique_ptr<web_app::WebAppUrlLoader>
       url_loader_;  // Loads the app to be installed.
 
+  // Produces retrievers used to obtain app data during installation.
+  base::RepeatingCallback<std::unique_ptr<web_app::WebAppDataRetriever>()>
+      data_retriever_factory_;
+
+  BrowserWindow* test_browser_window_ = nullptr;
+
   base::WeakPtrFactory<WebKioskAppLauncher> weak_ptr_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(WebKioskAppLauncher);
 };
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher_unittest.cc b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher_unittest.cc
new file mode 100644
index 0000000..611f3e72
--- /dev/null
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_launcher_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright 2020 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/app_mode/web_app/web_kiosk_app_launcher.h"
+#include <memory>
+
+#include "base/test/gmock_callback_support.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
+#include "chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.h"
+#include "chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_manager.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/web_applications/test/test_data_retriever.h"
+#include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
+#include "chrome/common/web_application_info.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::base::test::RunClosure;
+
+namespace chromeos {
+
+class MockAppLauncherDelegate : public WebKioskAppLauncher::Delegate {
+ public:
+  MockAppLauncherDelegate() = default;
+  ~MockAppLauncherDelegate() override = default;
+
+  MOCK_METHOD0(InitializeNetwork, void());
+  MOCK_METHOD0(OnAppStartedInstalling, void());
+  MOCK_METHOD0(OnAppPrepared, void());
+  MOCK_METHOD0(OnAppInstallFailed, void());
+  MOCK_METHOD0(OnAppLaunched, void());
+  MOCK_METHOD0(OnAppLaunchFailed, void());
+};
+
+const char kAppEmail[] = "lala@example.com";
+const char kAppInstallUrl[] = "https://example.com";
+const char kAppLaunchUrl[] = "https://example.com/launch";
+const char kAppLaunchBadUrl[] = "https://badexample.com";
+const char kAppTitle[] = "app";
+
+std::unique_ptr<web_app::WebAppDataRetriever> CreateDataRetrieverWithData(
+    const GURL& url) {
+  auto data_retriever = std::make_unique<web_app::TestDataRetriever>();
+  auto info = std::make_unique<WebApplicationInfo>();
+  info->app_url = url;
+  info->title = base::UTF8ToUTF16(kAppTitle);
+  data_retriever->SetRendererWebApplicationInfo(std::move(info));
+  return std::unique_ptr<web_app::WebAppDataRetriever>(
+      std::move(data_retriever));
+}
+
+class AppWindowCloser : public BrowserListObserver {
+ public:
+  AppWindowCloser() { BrowserList::AddObserver(this); }
+
+  ~AppWindowCloser() override { BrowserList::RemoveObserver(this); }
+
+  void OnBrowserAdded(Browser* browser) override { app_browser_ = browser; }
+
+  void OnBrowserRemoved(Browser* browser) override {
+    closed_ = true;
+    waiter.Quit();
+  }
+
+  void Close() {
+    DCHECK(app_browser_);
+    app_browser_->tab_strip_model()->CloseAllTabs();
+    delete app_browser_;
+    if (!closed_)
+      waiter.Run();
+  }
+
+ private:
+  bool closed_ = false;
+  Browser* app_browser_ = nullptr;
+  base::RunLoop waiter;
+};
+
+class WebKioskAppLauncherTest : public ChromeRenderViewHostTestHarness {
+ public:
+  WebKioskAppLauncherTest()
+      : ChromeRenderViewHostTestHarness(),
+        local_state_(TestingBrowserProcess::GetGlobal()) {}
+  ~WebKioskAppLauncherTest() override {}
+
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    app_manager_ = std::make_unique<WebKioskAppManager>();
+    delegate_ = std::make_unique<MockAppLauncherDelegate>();
+    launcher_ =
+        std::make_unique<WebKioskAppLauncher>(profile(), delegate_.get());
+
+    browser_window_ = new TestBrowserWindow();
+    new TestBrowserWindowOwner(browser_window_);
+    browser_window_->SetNativeWindow(new aura::Window(nullptr));
+
+    launcher_->SetBrowserWindowForTesting(browser_window_);
+    url_loader_ = new web_app::TestWebAppUrlLoader();
+    launcher_->SetUrlLoaderForTesting(
+        std::unique_ptr<web_app::TestWebAppUrlLoader>(url_loader_));
+
+    closer_.reset(new AppWindowCloser());
+  }
+
+  void TearDown() override {
+    closer_.reset();
+    launcher_.reset();
+    delegate_.reset();
+    app_manager_.reset();
+    ChromeRenderViewHostTestHarness::TearDown();
+  }
+
+  void SetupAppData(bool installed) {
+    account_id_ = AccountId::FromUserEmail(kAppEmail);
+    app_manager_->AddAppForTesting(account_id_, GURL(kAppInstallUrl));
+
+    if (installed) {
+      auto info = std::make_unique<WebApplicationInfo>();
+      info->app_url = GURL(kAppLaunchUrl);
+      info->title = base::UTF8ToUTF16(kAppTitle);
+      app_manager_->UpdateAppByAccountId(account_id_, std::move(info));
+    }
+  }
+
+  void SetupInstallData() {
+    url_loader_->SetNextLoadUrlResult(
+        GURL(kAppInstallUrl), web_app::WebAppUrlLoader::Result::kUrlLoaded);
+    launcher_->SetDataRetrieverFactoryForTesting(
+        base::BindRepeating(&CreateDataRetrieverWithData, GURL(kAppLaunchUrl)));
+  }
+
+  void SetupBadInstallData() {
+    url_loader_->SetNextLoadUrlResult(
+        GURL(kAppInstallUrl), web_app::WebAppUrlLoader::Result::kUrlLoaded);
+    launcher_->SetDataRetrieverFactoryForTesting(base::BindRepeating(
+        &CreateDataRetrieverWithData, GURL(kAppLaunchBadUrl)));
+  }
+
+  void SetupNotLoadedAppData() {
+    url_loader_->SetNextLoadUrlResult(
+        GURL(kAppInstallUrl),
+        web_app::WebAppUrlLoader::Result::kFailedPageTookTooLong);
+  }
+
+  const WebKioskAppData* app_data() {
+    return app_manager_->GetAppByAccountId(account_id_);
+  }
+
+  void CloseAppWindow() {
+    // Wait for it to be closed.
+    closer_->Close();
+  }
+
+  MockAppLauncherDelegate* delegate() { return delegate_.get(); }
+  WebKioskAppLauncher* launcher() { return launcher_.get(); }
+
+ protected:
+  AccountId account_id_;
+  web_app::TestWebAppUrlLoader* url_loader_;  // Owned by |launcher_|.
+
+ private:
+  std::unique_ptr<WebKioskAppManager> app_manager_;
+  ScopedTestingLocalState local_state_;
+
+  TestBrowserWindow* browser_window_;
+  std::unique_ptr<MockAppLauncherDelegate> delegate_;
+  std::unique_ptr<WebKioskAppLauncher> launcher_;
+  std::unique_ptr<AppWindowCloser> closer_;
+};
+
+TEST_F(WebKioskAppLauncherTest, NormalFlowNotInstalled) {
+  SetupAppData(/*installed*/ false);
+
+  base::RunLoop loop1;
+  EXPECT_CALL(*delegate(), InitializeNetwork())
+      .WillOnce(RunClosure(loop1.QuitClosure()));
+  launcher()->Initialize(account_id_);
+  loop1.Run();
+
+  SetupInstallData();
+
+  base::RunLoop loop2;
+  EXPECT_CALL(*delegate(), OnAppStartedInstalling());
+  EXPECT_CALL(*delegate(), OnAppPrepared())
+      .WillOnce(RunClosure(loop2.QuitClosure()));
+  launcher()->ContinueWithNetworkReady();
+  loop2.Run();
+
+  EXPECT_EQ(app_data()->status(), WebKioskAppData::STATUS_INSTALLED);
+  EXPECT_EQ(app_data()->launch_url(), kAppLaunchUrl);
+
+  base::RunLoop loop3;
+  EXPECT_CALL(*delegate(), OnAppLaunched())
+      .WillOnce(RunClosure(loop3.QuitClosure()));
+  launcher()->LaunchApp();
+  loop3.Run();
+
+  CloseAppWindow();
+}
+
+TEST_F(WebKioskAppLauncherTest, NormalFlowAlreadyInstalled) {
+  SetupAppData(/*installed*/ true);
+  base::RunLoop loop1;
+  EXPECT_CALL(*delegate(), OnAppPrepared())
+      .WillOnce(RunClosure(loop1.QuitClosure()));
+  launcher()->Initialize(account_id_);
+  loop1.Run();
+
+  base::RunLoop loop2;
+  EXPECT_CALL(*delegate(), OnAppLaunched())
+      .WillOnce(RunClosure(loop2.QuitClosure()));
+  launcher()->LaunchApp();
+  loop2.Run();
+
+  CloseAppWindow();
+}
+
+TEST_F(WebKioskAppLauncherTest, NormalFlowBadLaunchUrl) {
+  SetupAppData(/*installed*/ false);
+
+  base::RunLoop loop1;
+  EXPECT_CALL(*delegate(), InitializeNetwork())
+      .WillOnce(RunClosure(loop1.QuitClosure()));
+  launcher()->Initialize(account_id_);
+  loop1.Run();
+
+  SetupBadInstallData();
+
+  base::RunLoop loop2;
+  EXPECT_CALL(*delegate(), OnAppStartedInstalling());
+  EXPECT_CALL(*delegate(), OnAppLaunchFailed())
+      .WillOnce(RunClosure(loop2.QuitClosure()));
+  launcher()->ContinueWithNetworkReady();
+  loop2.Run();
+
+  EXPECT_NE(app_data()->status(), WebKioskAppData::STATUS_INSTALLED);
+}
+
+TEST_F(WebKioskAppLauncherTest, InstallationRestarted) {
+  SetupAppData(/*installed*/ false);
+  // Freezes url requests until they are manually processed.
+  url_loader_->SaveLoadUrlRequests();
+
+  base::RunLoop loop1;
+  EXPECT_CALL(*delegate(), InitializeNetwork())
+      .WillOnce(RunClosure(loop1.QuitClosure()));
+  launcher()->Initialize(account_id_);
+  loop1.Run();
+
+  SetupInstallData();
+
+  EXPECT_CALL(*delegate(), OnAppStartedInstalling());
+  launcher()->ContinueWithNetworkReady();
+
+  launcher()->CancelCurrentInstallation();
+
+  // App should not be installed yet.
+  EXPECT_NE(app_data()->status(), WebKioskAppData::STATUS_INSTALLED);
+
+  // We should not receive any status updates now.
+  url_loader_->ProcessLoadUrlRequests();
+
+  SetupInstallData();
+
+  base::RunLoop loop2;
+  EXPECT_CALL(*delegate(), OnAppStartedInstalling()).Times(1);
+  EXPECT_CALL(*delegate(), OnAppPrepared())
+      .Times(1)
+      .WillOnce(RunClosure(loop2.QuitClosure()));
+  launcher()->ContinueWithNetworkReady();
+  url_loader_->ProcessLoadUrlRequests();
+  loop2.Run();
+
+  EXPECT_EQ(app_data()->status(), WebKioskAppData::STATUS_INSTALLED);
+
+  base::RunLoop loop3;
+  EXPECT_CALL(*delegate(), OnAppLaunched())
+      .WillOnce(RunClosure(loop3.QuitClosure()));
+  launcher()->LaunchApp();
+  loop3.Run();
+
+  CloseAppWindow();
+}
+
+TEST_F(WebKioskAppLauncherTest, UrlNotLoaded) {
+  SetupAppData(/*installed*/ false);
+
+  base::RunLoop loop1;
+  EXPECT_CALL(*delegate(), InitializeNetwork())
+      .WillOnce(RunClosure(loop1.QuitClosure()));
+  launcher()->Initialize(account_id_);
+  loop1.Run();
+
+  SetupNotLoadedAppData();
+
+  base::RunLoop loop2;
+  EXPECT_CALL(*delegate(), OnAppStartedInstalling());
+  EXPECT_CALL(*delegate(), OnAppInstallFailed())
+      .WillOnce(RunClosure(loop2.QuitClosure()));
+  launcher()->ContinueWithNetworkReady();
+  loop2.Run();
+
+  EXPECT_NE(app_data()->status(), WebKioskAppData::STATUS_INSTALLED);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 66c5e7e1..f7a499da 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -545,6 +545,14 @@
   return result;
 }
 
+std::vector<api::autotest_private::Bounds> ToBoundsDictionaryList(
+    const std::vector<gfx::Rect>& items_bounds) {
+  std::vector<api::autotest_private::Bounds> bounds_list;
+  for (const gfx::Rect& bounds : items_bounds)
+    bounds_list.push_back(ToBoundsDictionary(bounds));
+  return bounds_list;
+}
+
 api::autotest_private::Location ToLocationDictionary(const gfx::Point& point) {
   api::autotest_private::Location result;
   result.x = point.x();
@@ -4149,6 +4157,8 @@
         ToBoundsDictionary(fetched_info.right_arrow_bounds);
     scrollable_shelf_ui_info.is_animating = fetched_info.is_animating;
     scrollable_shelf_ui_info.is_overflow = fetched_info.is_overflow;
+    scrollable_shelf_ui_info.icons_bounds_in_screen =
+        ToBoundsDictionaryList(fetched_info.icons_bounds_in_screen);
 
     if (state.scroll_distance) {
       scrollable_shelf_ui_info.target_main_axis_offset =
diff --git a/chrome/browser/chromeos/input_method/DEPS b/chrome/browser/chromeos/input_method/DEPS
index 771e608..731b4e9 100644
--- a/chrome/browser/chromeos/input_method/DEPS
+++ b/chrome/browser/chromeos/input_method/DEPS
@@ -19,6 +19,10 @@
     "+ash/shell.h",
     "+ash/wm/window_util.h",
   ],
+  "suggestion_window_controller_impl\.cc": [
+    "+ash/shell.h",
+    "+ash/wm/window_util.h",
+  ],
 
   # TODO(erikwright): Bring this list to zero.
   # Do not add to the list of temporarily-allowed dependencies below,
diff --git a/chrome/browser/chromeos/input_method/assistive_suggester.cc b/chrome/browser/chromeos/input_method/assistive_suggester.cc
index 2b8b356..d1da6f8 100644
--- a/chrome/browser/chromeos/input_method/assistive_suggester.cc
+++ b/chrome/browser/chromeos/input_method/assistive_suggester.cc
@@ -26,20 +26,14 @@
 const char kAssistAddressPrefix[] = "my address is ";
 const char kAssistPhoneNumberPrefix[] = "my phone number is ";
 
-// Must match with IMEAssistiveAction in enums.xml
-enum class AssistiveType {
-  kGenericAction = 0,
-  kPersonalEmail = 1,
-  kPersonalAddress = 2,
-  kPersonalPhoneNumber = 3,
-  kPersonalName = 4,
-  kMaxValue = kPersonalName,
-};
-
-void RecordAssitiveConverage(AssistiveType type) {
+void RecordAssistiveCoverage(AssistiveType type) {
   base::UmaHistogramEnumeration("InputMethod.Assistive.Coverage", type);
 }
 
+void RecordAssistiveSuccess(AssistiveType type) {
+  base::UmaHistogramEnumeration("InputMethod.Assistive.Success", type);
+}
+
 AssistiveType ProposeAssistiveAction(const base::string16& text) {
   AssistiveType action = AssistiveType::kGenericAction;
   if (base::EndsWith(text, base::UTF8ToUTF16(kAssistEmailPrefix),
@@ -92,7 +86,9 @@
   if (suggestion_shown_ && event.type == kKeydown) {
     suggestion_shown_ = false;
     if (event.key == "Tab" || event.key == "Right") {
-      engine_->ConfirmCompositionText(false, false);
+      std::string error;
+      engine_->AcceptSuggestion(context_id_, &error);
+      RecordAssistiveSuccess(proposed_action_type_);
       return true;
     }
     DismissSuggestion();
@@ -102,7 +98,7 @@
   return false;
 }
 
-void AssistiveSuggester::RecordAssitiveCoverageMetrics(
+void AssistiveSuggester::RecordAssistiveCoverageMetrics(
     const base::string16& text,
     int cursor_pos,
     int anchor_pos) {
@@ -114,7 +110,7 @@
         text.substr(start_pos, cursor_pos - start_pos);
     AssistiveType action = ProposeAssistiveAction(text_before_cursor);
     if (action != AssistiveType::kGenericAction)
-      RecordAssitiveConverage(action);
+      RecordAssistiveCoverage(action);
   }
 }
 
@@ -126,8 +122,13 @@
     return false;
   }
 
-  if (context_id_ == -1 || suggestion_shown_)
+  if (context_id_ == -1)
     return false;
+
+  if (suggestion_shown_) {
+    suggestion_shown_ = false;
+    DismissSuggestion();
+  }
   Suggest(text, cursor_pos, anchor_pos);
   return suggestion_shown_;
 }
@@ -160,6 +161,8 @@
   if (action == AssistiveType::kGenericAction)
     return base::EmptyString16();
 
+  proposed_action_type_ = action;
+
   if (action == AssistiveType::kPersonalEmail)
     return base::UTF8ToUTF16(profile_->GetProfileUserName());
 
@@ -192,9 +195,7 @@
 
 void AssistiveSuggester::ShowSuggestion(const base::string16& text) {
   std::string error;
-  std::vector<InputMethodEngineBase::SegmentInfo> segments;
-  engine_->SetComposition(context_id_, base::UTF16ToUTF8(text).c_str(), 0, 0, 0,
-                          segments, &error);
+  engine_->SetSuggestion(context_id_, text, &error);
   if (!error.empty()) {
     LOG(ERROR) << "Fail to show suggestion. " << error;
   }
@@ -202,7 +203,7 @@
 
 void AssistiveSuggester::DismissSuggestion() {
   std::string error;
-  engine_->ClearComposition(context_id_, &error);
+  engine_->DismissSuggestion(context_id_, &error);
   if (!error.empty()) {
     LOG(ERROR) << "Failed to dismiss suggestion. " << error;
   }
diff --git a/chrome/browser/chromeos/input_method/assistive_suggester.h b/chrome/browser/chromeos/input_method/assistive_suggester.h
index a9c9ac4f..6963b9c 100644
--- a/chrome/browser/chromeos/input_method/assistive_suggester.h
+++ b/chrome/browser/chromeos/input_method/assistive_suggester.h
@@ -18,6 +18,16 @@
 
 namespace chromeos {
 
+// Must match with IMEAssistiveAction in enums.xml
+enum class AssistiveType {
+  kGenericAction = 0,
+  kPersonalEmail = 1,
+  kPersonalAddress = 2,
+  kPersonalPhoneNumber = 3,
+  kPersonalName = 4,
+  kMaxValue = kPersonalName,
+};
+
 // An agent to suggest assistive information when the user types, and adopt or
 // dismiss the suggestion according to the user action.
 class AssistiveSuggester {
@@ -32,9 +42,9 @@
 
   // Checks the text before cursor, emits metric if any assistive prefix is
   // matched.
-  void RecordAssitiveCoverageMetrics(const base::string16& text,
-                                     int cursor_pos,
-                                     int anchor_pos);
+  void RecordAssistiveCoverageMetrics(const base::string16& text,
+                                      int cursor_pos,
+                                      int anchor_pos);
 
   // Called when a surrounding text is changed.
   // Returns true if it changes the surrounding text, e.g. a suggestion is
@@ -74,6 +84,9 @@
   // If we are showing a suggestion right now.
   bool suggestion_shown_ = false;
 
+  // Assistive type of the last proposed assistive action.
+  AssistiveType proposed_action_type_ = AssistiveType::kGenericAction;
+
   // If the suggestion is dismissed by the user, this is necessary so that we
   // will not reshow the suggestion immediately after the user dismisses it.
   bool suggestion_dismissed_ = false;
diff --git a/chrome/browser/chromeos/input_method/input_method_engine.cc b/chrome/browser/chromeos/input_method/input_method_engine.cc
index 526a4977..bffaefc8 100644
--- a/chrome/browser/chromeos/input_method/input_method_engine.cc
+++ b/chrome/browser/chromeos/input_method/input_method_engine.cc
@@ -43,6 +43,7 @@
 const char kErrorNotActive[] = "IME is not active.";
 const char kErrorWrongContext[] = "Context is not active.";
 const char kCandidateNotFound[] = "Candidate not found.";
+const char kSuggestionNotFound[] = "Suggestion not found.";
 
 // The default entry number of a page in CandidateWindowProperty.
 const int kDefaultPageSize = 9;
@@ -221,6 +222,67 @@
   return true;
 }
 
+bool InputMethodEngine::SetSuggestion(int context_id,
+                                      const base::string16& text,
+                                      std::string* error) {
+  if (!IsActive()) {
+    *error = kErrorNotActive;
+    return false;
+  }
+  if (context_id != context_id_ || context_id_ == -1) {
+    *error = kErrorWrongContext;
+    return false;
+  }
+
+  IMESuggestionWindowHandlerInterface* sw_handler =
+      ui::IMEBridge::Get()->GetSuggestionWindowHandler();
+  if (sw_handler)
+    sw_handler->Show(text);
+  return true;
+}
+
+bool InputMethodEngine::DismissSuggestion(int context_id, std::string* error) {
+  if (!IsActive()) {
+    *error = kErrorNotActive;
+    return false;
+  }
+  if (context_id != context_id_ || context_id_ == -1) {
+    *error = kErrorWrongContext;
+    return false;
+  }
+
+  IMESuggestionWindowHandlerInterface* sw_handler =
+      ui::IMEBridge::Get()->GetSuggestionWindowHandler();
+  if (sw_handler)
+    sw_handler->Hide();
+  return true;
+}
+
+bool InputMethodEngine::AcceptSuggestion(int context_id, std::string* error) {
+  if (!IsActive()) {
+    *error = kErrorNotActive;
+    return false;
+  }
+  if (context_id != context_id_ || context_id_ == -1) {
+    *error = kErrorWrongContext;
+    return false;
+  }
+
+  IMESuggestionWindowHandlerInterface* sw_handler =
+      ui::IMEBridge::Get()->GetSuggestionWindowHandler();
+  if (sw_handler) {
+    base::string16 suggestion_text = sw_handler->GetText();
+    if (suggestion_text.empty()) {
+      *error = kSuggestionNotFound;
+      return false;
+    }
+    CommitText(context_id_, (base::UTF16ToUTF8(suggestion_text)).c_str(),
+               error);
+    sw_handler->Hide();
+  }
+  return true;
+}
+
 bool InputMethodEngine::SetMenuItems(
     const std::vector<input_method::InputMethodManager::MenuItem>& items,
     std::string* error) {
diff --git a/chrome/browser/chromeos/input_method/input_method_engine.h b/chrome/browser/chromeos/input_method/input_method_engine.h
index da34364ce..ef7ecb2 100644
--- a/chrome/browser/chromeos/input_method/input_method_engine.h
+++ b/chrome/browser/chromeos/input_method/input_method_engine.h
@@ -116,6 +116,17 @@
   // Set the position of the cursor in the candidate window.
   bool SetCursorPosition(int context_id, int candidate_id, std::string* error);
 
+  // Dismiss suggestion window.
+  bool DismissSuggestion(int context_id, std::string* error);
+
+  // Set and show suggestion window.
+  bool SetSuggestion(int context_id,
+                     const base::string16& text,
+                     std::string* error);
+
+  // Commit the suggestion and hide the window.
+  bool AcceptSuggestion(int context_id, std::string* error);
+
   // Set the list of items that appears in the language menu when this IME is
   // active.
   bool SetMenuItems(
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
index 67c9ba2..8badd952 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/browser_process_platform_part_chromeos.h"
 #include "chrome/browser/chromeos/input_method/candidate_window_controller.h"
 #include "chrome/browser/chromeos/input_method/component_extension_ime_manager_impl.h"
+#include "chrome/browser/chromeos/input_method/suggestion_window_controller.h"
 #include "chrome/browser/chromeos/language_preferences.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -882,8 +883,10 @@
   // Initialize candidate window controller and widgets such as
   // candidate window, infolist and mode indicator.  Note, mode
   // indicator is used by only keyboard layout input methods.
-  if (state_.get() == state)
+  if (state_.get() == state) {
     MaybeInitializeCandidateWindowController();
+    MaybeInitializeSuggestionWindowController();
+  }
 }
 
 void InputMethodManagerImpl::SetState(
@@ -899,6 +902,7 @@
     // candidate window, infolist and mode indicator.  Note, mode
     // indicator is used by only keyboard layout input methods.
     MaybeInitializeCandidateWindowController();
+    MaybeInitializeSuggestionWindowController();
 
     // Always call ChangeInputMethodInternal even when the input method id
     // remain unchanged, because onActivate event needs to be sent to IME
@@ -995,8 +999,12 @@
 
 void InputMethodManagerImpl::SetUISessionState(UISessionState new_ui_session) {
   ui_session_ = new_ui_session;
-  if (ui_session_ == STATE_TERMINATING && candidate_window_controller_.get())
-    candidate_window_controller_.reset();
+  if (ui_session_ == STATE_TERMINATING) {
+    if (candidate_window_controller_.get())
+      candidate_window_controller_.reset();
+    if (suggestion_window_controller_.get())
+      suggestion_window_controller_.reset();
+  }
 }
 
 void InputMethodManagerImpl::OnUserAddingStarted() {
@@ -1225,6 +1233,11 @@
   candidate_window_controller_->AddObserver(this);
 }
 
+void InputMethodManagerImpl::SetSuggestionWindowControllerForTesting(
+    SuggestionWindowController* suggestion_window_controller) {
+  suggestion_window_controller_.reset(suggestion_window_controller);
+}
+
 void InputMethodManagerImpl::SetImeKeyboardForTesting(ImeKeyboard* keyboard) {
   keyboard_.reset(keyboard);
 }
@@ -1286,6 +1299,14 @@
   candidate_window_controller_->AddObserver(this);
 }
 
+void InputMethodManagerImpl::MaybeInitializeSuggestionWindowController() {
+  if (suggestion_window_controller_.get())
+    return;
+
+  suggestion_window_controller_.reset(
+      SuggestionWindowController::CreateSuggestionWindowController());
+}
+
 void InputMethodManagerImpl::NotifyImeMenuItemsChanged(
     const std::string& engine_id,
     const std::vector<InputMethodManager::MenuItem>& items) {
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.h b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
index 0fe1e01..ac7a1c4b 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.h
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.h
@@ -17,6 +17,7 @@
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/chromeos/input_method/candidate_window_controller.h"
 #include "chrome/browser/chromeos/input_method/ime_service_connector.h"
+#include "chrome/browser/chromeos/input_method/suggestion_window_controller.h"
 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
 #include "chrome/browser/profiles/profile.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
@@ -240,6 +241,9 @@
   // Sets |candidate_window_controller_|.
   void SetCandidateWindowControllerForTesting(
       CandidateWindowController* candidate_window_controller);
+  // Sets |suggestion_window_controller_|.
+  void SetSuggestionWindowControllerForTesting(
+      SuggestionWindowController* suggestion_window_controller);
   // Sets |keyboard_|.
   void SetImeKeyboardForTesting(ImeKeyboard* keyboard);
   // Initialize |component_extension_manager_|.
@@ -257,6 +261,9 @@
   // Creates and initializes |candidate_window_controller_| if it hasn't been
   // done.
   void MaybeInitializeCandidateWindowController();
+  // Creates and initializes |suggestion_window_controller_| if it hasn't been
+  // done.
+  void MaybeInitializeSuggestionWindowController();
 
   // Returns Input Method that best matches given id.
   const InputMethodDescriptor* LookupInputMethod(
@@ -304,6 +311,9 @@
   // The candidate window.  This will be deleted when the APP_TERMINATING
   // message is sent.
   std::unique_ptr<CandidateWindowController> candidate_window_controller_;
+  // The suggestion window.  This will be deleted when the APP_TERMINATING
+  // message is sent.
+  std::unique_ptr<SuggestionWindowController> suggestion_window_controller_;
 
   // An object which provides miscellaneous input method utility functions. Note
   // that |util_| is required to initialize |keyboard_|.
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
index 9022bfb..708078ab 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/test/task_environment.h"
 #include "chrome/browser/chromeos/input_method/mock_candidate_window_controller.h"
 #include "chrome/browser/chromeos/input_method/mock_input_method_engine.h"
+#include "chrome/browser/chromeos/input_method/mock_suggestion_window_controller.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/ime_controller_client.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.h"
@@ -135,8 +136,8 @@
   InputMethodManagerImplTest()
       : delegate_(nullptr),
         candidate_window_controller_(nullptr),
-        keyboard_(nullptr) {
-  }
+        suggestion_window_controller_(nullptr),
+        keyboard_(nullptr) {}
 
   ~InputMethodManagerImplTest() override = default;
 
@@ -147,6 +148,9 @@
     manager_.reset(new InputMethodManagerImpl(
         std::unique_ptr<InputMethodDelegate>(delegate_), false));
     manager_->GetInputMethodUtil()->UpdateHardwareLayoutCache();
+    suggestion_window_controller_ = new MockSuggestionWindowController;
+    manager_->SetSuggestionWindowControllerForTesting(
+        suggestion_window_controller_);
     candidate_window_controller_ = new MockCandidateWindowController;
     manager_->SetCandidateWindowControllerForTesting(
         candidate_window_controller_);
@@ -177,6 +181,7 @@
 
     delegate_ = nullptr;
     candidate_window_controller_ = nullptr;
+    suggestion_window_controller_ = nullptr;
     keyboard_ = nullptr;
     manager_.reset();
   }
@@ -367,6 +372,7 @@
   std::unique_ptr<InputMethodManagerImpl> manager_;
   FakeInputMethodDelegate* delegate_;
   MockCandidateWindowController* candidate_window_controller_;
+  MockSuggestionWindowController* suggestion_window_controller_;
   std::unique_ptr<MockInputMethodEngine> mock_engine_handler_;
   FakeImeKeyboard* keyboard_;
   MockComponentExtIMEManagerDelegate* mock_delegate_;
diff --git a/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.cc b/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.cc
new file mode 100644
index 0000000..6ed08bf
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2012 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/input_method/mock_suggestion_window_controller.h"
+
+namespace chromeos {
+namespace input_method {
+
+MockSuggestionWindowController::MockSuggestionWindowController() {}
+
+MockSuggestionWindowController::~MockSuggestionWindowController() = default;
+
+}  // namespace input_method
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.h b/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.h
new file mode 100644
index 0000000..d4f4e46
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/mock_suggestion_window_controller.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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_INPUT_METHOD_MOCK_SUGGESTION_WINDOW_CONTROLLER_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_MOCK_SUGGESTION_WINDOW_CONTROLLER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/input_method/suggestion_window_controller.h"
+
+namespace chromeos {
+namespace input_method {
+
+// The mock SuggestionWindowController implementation for testing.
+class MockSuggestionWindowController : public SuggestionWindowController {
+ public:
+  MockSuggestionWindowController();
+  ~MockSuggestionWindowController() override;
+
+  DISALLOW_COPY_AND_ASSIGN(MockSuggestionWindowController);
+};
+
+}  // namespace input_method
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_MOCK_SUGGESTION_WINDOW_CONTROLLER_H_
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine.cc b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
index 68a1f32..b8333fb2 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
@@ -199,8 +199,8 @@
     int cursor_pos,
     int anchor_pos,
     int offset_pos) {
-  assistive_suggester_->RecordAssitiveCoverageMetrics(text, cursor_pos,
-                                                      anchor_pos);
+  assistive_suggester_->RecordAssistiveCoverageMetrics(text, cursor_pos,
+                                                       anchor_pos);
   if (IsAssistPersonalInfoEnabled()) {
     // If |assistive_suggester_| changes the surrounding text, no longer need
     // to call the following function, as the information is out-dated.
diff --git a/chrome/browser/chromeos/input_method/suggestion_window_controller.cc b/chrome/browser/chromeos/input_method/suggestion_window_controller.cc
new file mode 100644
index 0000000..c8667dd
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggestion_window_controller.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2020 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/input_method/suggestion_window_controller.h"
+
+#include "chrome/browser/chromeos/input_method/suggestion_window_controller_impl.h"
+
+namespace chromeos {
+namespace input_method {
+
+// static
+SuggestionWindowController*
+SuggestionWindowController::CreateSuggestionWindowController() {
+  return new SuggestionWindowControllerImpl;
+}
+
+}  // namespace input_method
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/suggestion_window_controller.h b/chrome/browser/chromeos/input_method/suggestion_window_controller.h
new file mode 100644
index 0000000..699060e
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggestion_window_controller.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2020 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.
+//
+// This file implements the input method suggestion window used on Chrome OS.
+
+#ifndef CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_H_
+
+namespace chromeos {
+namespace input_method {
+
+// SuggestionWindowController is used for controlling the input method
+// suggestion window. Once the initialization is done, the controller
+// starts monitoring signals sent from the the background input method
+// daemon, and shows and hides the suggestion window as needed. Upon
+// deletion of the object, monitoring stops and the view used for
+// rendering the suggestion view is deleted.
+class SuggestionWindowController {
+ public:
+  virtual ~SuggestionWindowController() = default;
+
+  // Gets an instance of SuggestionWindowController. Caller has to delete the
+  // returned object.
+  static SuggestionWindowController* CreateSuggestionWindowController();
+};
+
+}  // namespace input_method
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_H_
diff --git a/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.cc b/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.cc
new file mode 100644
index 0000000..49b674f
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2020 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/input_method/suggestion_window_controller_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/shell.h"           // mash-ok
+#include "ash/wm/window_util.h"  // mash-ok
+#include "base/logging.h"
+#include "ui/base/ime/ime_bridge.h"
+#include "ui/chromeos/ime/infolist_window.h"
+#include "ui/views/widget/widget.h"
+
+namespace chromeos {
+namespace input_method {
+
+namespace {}  // namespace
+
+SuggestionWindowControllerImpl::SuggestionWindowControllerImpl() {
+  ui::IMEBridge::Get()->SetSuggestionWindowHandler(this);
+}
+
+SuggestionWindowControllerImpl::~SuggestionWindowControllerImpl() {
+  ui::IMEBridge::Get()->SetSuggestionWindowHandler(nullptr);
+}
+
+void SuggestionWindowControllerImpl::Init() {
+  if (suggestion_window_view_)
+    return;
+
+  gfx::NativeView parent = nullptr;
+
+  aura::Window* active_window = ash::window_util::GetActiveWindow();
+  // Use VirtualKeyboardContainer so that it works even with a system modal
+  // dialog.
+  parent = ash::Shell::GetContainer(
+      active_window ? active_window->GetRootWindow()
+                    : ash::Shell::GetRootWindowForNewWindows(),
+      ash::kShellWindowId_VirtualKeyboardContainer);
+  suggestion_window_view_ = new ui::ime::SuggestionWindowView(
+      parent, ash::kShellWindowId_VirtualKeyboardContainer);
+  views::Widget* widget = suggestion_window_view_->InitWidget();
+  widget->AddObserver(this);
+  widget->Show();
+}
+
+void SuggestionWindowControllerImpl::OnWidgetClosing(views::Widget* widget) {
+  if (suggestion_window_view_ &&
+      widget == suggestion_window_view_->GetWidget()) {
+    widget->RemoveObserver(this);
+    suggestion_window_view_ = nullptr;
+  }
+}
+
+void SuggestionWindowControllerImpl::Hide() {
+  suggestion_text_ = base::EmptyString16();
+  if (suggestion_window_view_)
+    suggestion_window_view_->GetWidget()->Close();
+}
+
+void SuggestionWindowControllerImpl::SetBounds(const gfx::Rect& cursor_bounds) {
+  if (suggestion_window_view_)
+    suggestion_window_view_->SetBounds(cursor_bounds);
+}
+
+void SuggestionWindowControllerImpl::FocusStateChanged() {
+  if (suggestion_window_view_)
+    Hide();
+}
+
+void SuggestionWindowControllerImpl::Show(const base::string16& text) {
+  if (!suggestion_window_view_)
+    Init();
+  suggestion_text_ = text;
+  suggestion_window_view_->Show(text);
+}
+
+base::string16 SuggestionWindowControllerImpl::GetText() const {
+  return suggestion_text_;
+}
+
+}  // namespace input_method
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.h b/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.h
new file mode 100644
index 0000000..2261d9a
--- /dev/null
+++ b/chrome/browser/chromeos/input_method/suggestion_window_controller_impl.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2020 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_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_IMPL_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/input_method/suggestion_window_controller.h"
+#include "ui/base/ime/ime_suggestion_window_handler_interface.h"
+#include "ui/chromeos/ime/suggestion_window_view.h"
+
+namespace views {
+class Widget;
+}  // namespace views
+
+namespace chromeos {
+namespace input_method {
+
+// The implementation of SuggestionWindowController.
+// SuggestionWindowController controls the SuggestionWindow.
+class SuggestionWindowControllerImpl
+    : public SuggestionWindowController,
+      public views::WidgetObserver,
+      public IMESuggestionWindowHandlerInterface {
+ public:
+  SuggestionWindowControllerImpl();
+  ~SuggestionWindowControllerImpl() override;
+
+  // IMESuggestionWindowHandlerInterface implementation.
+  void SetBounds(const gfx::Rect& cursor_bounds) override;
+  void Show(const base::string16& text) override;
+  void Hide() override;
+  base::string16 GetText() const override;
+  void FocusStateChanged() override;
+  void OnWidgetClosing(views::Widget* widget) override;
+
+  void Init();
+
+  // The suggestion window view.
+  ui::ime::SuggestionWindowView* suggestion_window_view_ = nullptr;
+
+  bool is_focused_ = false;
+  base::string16 suggestion_text_;
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestionWindowControllerImpl);
+};
+
+}  // namespace input_method
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_INPUT_METHOD_SUGGESTION_WINDOW_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/chromeos/printing/printer_error_codes.cc b/chrome/browser/chromeos/printing/printer_error_codes.cc
index 8765b559..72c05b26 100644
--- a/chrome/browser/chromeos/printing/printer_error_codes.cc
+++ b/chrome/browser/chromeos/printing/printer_error_codes.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/chromeos/printing/printer_error_codes.h"
 
 #include "printing/backend/cups_jobs.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 
 namespace chromeos {
 
diff --git a/chrome/browser/chromeos/printing/printer_info.h b/chrome/browser/chromeos/printing/printer_info.h
index 741a117..9b91320 100644
--- a/chrome/browser/chromeos/printing/printer_info.h
+++ b/chrome/browser/chromeos/printing/printer_info.h
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "printing/printer_query_result_chromeos.h"
+#include "printing/printer_query_result.h"
 
 namespace printing {
 struct PrinterStatus;
diff --git a/chrome/browser/chromeos/printing/printer_info_cups.cc b/chrome/browser/chromeos/printing/printer_info_cups.cc
index 4a4b9a5c..f7010a1 100644
--- a/chrome/browser/chromeos/printing/printer_info_cups.cc
+++ b/chrome/browser/chromeos/printing/printer_info_cups.cc
@@ -18,7 +18,7 @@
 #include "base/version.h"
 #include "chrome/browser/chromeos/printing/printer_info.h"
 #include "printing/backend/cups_jobs.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 
 namespace {
 
diff --git a/chrome/browser/chromeos/printing/printer_info_stub.cc b/chrome/browser/chromeos/printing/printer_info_stub.cc
index d03b418c..89730f07 100644
--- a/chrome/browser/chromeos/printing/printer_info_stub.cc
+++ b/chrome/browser/chromeos/printing/printer_info_stub.cc
@@ -8,7 +8,7 @@
 #include "base/logging.h"
 #include "base/task/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 
 namespace chromeos {
 
diff --git a/chrome/browser/dev_ui_browser_resources.grd b/chrome/browser/dev_ui_browser_resources.grd
index 402fb5d6..1ea7216 100644
--- a/chrome/browser/dev_ui_browser_resources.grd
+++ b/chrome/browser/dev_ui_browser_resources.grd
@@ -40,6 +40,7 @@
       <include name="IDR_INTERVENTIONS_INTERNALS_UNSUPPORTED_PAGE_HTML" file="resources\interventions_internals\unsupported_page.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
       <include name="IDR_LOCAL_STATE_HTML" file="resources\local_state\local_state.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_LOCAL_STATE_JS" file="resources\local_state\local_state.js" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_MEDIA_DATA_TABLE_JS" file="resources\media\media_data_table.js" flattenhtml="true" type="BINDATA" compress="gzip" />
       <include name="IDR_MEDIA_ENGAGEMENT_HTML" file="resources\media\media_engagement.html" flattenhtml="true" type="BINDATA" compress="gzip" allowexternalscript="true" />
       <include name="IDR_MEDIA_ENGAGEMENT_JS" file="resources\media\media_engagement.js" flattenhtml="true" type="BINDATA" compress="gzip" />
       <include name="IDR_MEDIA_ENGAGEMENT_SCORE_DETAILS_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\media\media_engagement_score_details.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
diff --git a/chrome/browser/local_search_service/local_search_service_proxy.cc b/chrome/browser/local_search_service/local_search_service_proxy.cc
index 8d7ae01..31b671c 100644
--- a/chrome/browser/local_search_service/local_search_service_proxy.cc
+++ b/chrome/browser/local_search_service/local_search_service_proxy.cc
@@ -16,11 +16,25 @@
 
 mojom::LocalSearchService* LocalSearchServiceProxy::GetLocalSearchService() {
   if (!local_search_service_impl_) {
-    local_search_service_impl_ = std::make_unique<LocalSearchServiceImpl>();
-    local_search_service_impl_->BindReceiver(
-        remote_.BindNewPipeAndPassReceiver());
+    CreateLocalSearchServiceAndBind();
   }
   return remote_.get();
 }
 
+LocalSearchServiceImpl* LocalSearchServiceProxy::GetLocalSearchServiceImpl() {
+  if (!local_search_service_impl_) {
+    // Need to bind |remote_| even if a client asks for the implementation
+    // directly.
+    CreateLocalSearchServiceAndBind();
+  }
+  return local_search_service_impl_.get();
+}
+
+void LocalSearchServiceProxy::CreateLocalSearchServiceAndBind() {
+  DCHECK(!local_search_service_impl_);
+  local_search_service_impl_ = std::make_unique<LocalSearchServiceImpl>();
+  local_search_service_impl_->BindReceiver(
+      remote_.BindNewPipeAndPassReceiver());
+}
+
 }  // namespace local_search_service
diff --git a/chrome/browser/local_search_service/local_search_service_proxy.h b/chrome/browser/local_search_service/local_search_service_proxy.h
index 5b7ba928..aa0b7cb 100644
--- a/chrome/browser/local_search_service/local_search_service_proxy.h
+++ b/chrome/browser/local_search_service/local_search_service_proxy.h
@@ -21,8 +21,10 @@
 
 class LocalSearchServiceImpl;
 
-// This class owns an implementation of LocalSearchService, but it only exposes
-// LocalSearchService through the mojo interface by returning a remote.
+// This class owns an implementation of LocalSearchService.
+// It exposes LocalSearchService through the mojo interface by returning a
+// remote. However, in-process clients can request implementation ptr directly.
+// TODO(jiameng): the next cl will remove mojo and will provide impl directly.
 class LocalSearchServiceProxy : public KeyedService {
  public:
   // Profile isn't required, hence can be nullptr in tests.
@@ -37,7 +39,12 @@
   // to |local_search_service_impl_|.
   mojom::LocalSearchService* GetLocalSearchService();
 
+  // For in-process clients, it could be more efficient to get the
+  // implementation ptr directly.
+  LocalSearchServiceImpl* GetLocalSearchServiceImpl();
+
  private:
+  void CreateLocalSearchServiceAndBind();
   std::unique_ptr<LocalSearchServiceImpl> local_search_service_impl_;
   mojo::Remote<mojom::LocalSearchService> remote_;
 
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index e36e0104..21f5c50 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -22,6 +22,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/lock.h"
 #include "base/task/post_task.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/metrics/user_action_tester.h"
 #include "base/test/test_timeouts.h"
 #include "base/thread_annotations.h"
 #include "base/threading/thread_restrictions.h"
@@ -31,6 +33,7 @@
 #include "chrome/browser/extensions/component_loader.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/metrics/subprocess_metrics_provider.h"
 #include "chrome/browser/pdf/pdf_extension_test_util.h"
 #include "chrome/browser/pdf/pdf_extension_util.h"
 #include "chrome/browser/plugins/plugin_prefs.h"
@@ -187,37 +190,6 @@
     return true;
   }
 
-  // Runs the extensions test at chrome/test/data/pdf/<filename> on the PDF file
-  // at chrome/test/data/pdf/<pdf_filename>, where |filename| is loaded as a JS
-  // module.
-  void RunTestsInJsModule(const std::string& filename,
-                          const std::string& pdf_filename) {
-    extensions::ResultCatcher catcher;
-
-    GURL url(embedded_test_server()->GetURL("/pdf/" + pdf_filename));
-
-    // It should be good enough to just navigate to the URL. But loading up the
-    // BrowserPluginGuest seems to happen asynchronously as there was flakiness
-    // being seen due to the BrowserPluginGuest not being available yet (see
-    // crbug.com/498077). So instead use LoadPdf() which ensures that the PDF is
-    // loaded before continuing.
-    WebContents* guest_contents = LoadPdfGetGuestContents(url);
-    ASSERT_TRUE(guest_contents);
-
-    constexpr char kModuleLoaderTemplate[] =
-        R"(var s = document.createElement('script');
-           s.type = 'module';
-           s.src = '_test_resources/%s';
-           document.body.appendChild(s);)";
-
-    ASSERT_TRUE(content::ExecuteScript(
-        guest_contents,
-        base::StringPrintf(kModuleLoaderTemplate, filename.c_str())));
-
-    if (!catcher.GetNextResult())
-      FAIL() << catcher.message();
-  }
-
   // Load the PDF at the given URL and ensure it has finished loading. Return
   // true if it loads successfully or false if it fails. If it doesn't finish
   // loading the test will hang. This is done from outside of the BrowserPlugin
@@ -386,23 +358,6 @@
         base::FilePath(ChromeContentClient::kPDFPluginPath),
         browser()->profile()->GetPath());
   }
-
-  // Installs the specified service worker and tests navigating to a PDF in its
-  // scope.
-  void RunServiceWorkerTest(const std::string& worker_path) {
-    // Install the service worker.
-    ui_test_utils::NavigateToURL(
-        browser(), embedded_test_server()->GetURL(
-                       "/service_worker/create_service_worker.html"));
-    EXPECT_EQ("DONE",
-              EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
-                     "register('" + worker_path + "', '/pdf');"));
-
-    // Navigate to a PDF in the service worker's scope. It should load.
-    RunTestsInJsModule("basic_test.js", "test.pdf");
-    // Ensure it loaded in a PPAPI process.
-    EXPECT_EQ(1, CountPDFProcesses());
-  }
 };
 
 class PDFExtensionTestWithTestGuestViewManager : public PDFExtensionTest {
@@ -513,19 +468,6 @@
 
 using PDFExtensionHitTestTest = PDFExtensionTest;
 
-class PDFAnnotationsTest : public PDFExtensionTest {
- public:
-  PDFAnnotationsTest() {}
-
- protected:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    PDFExtensionTest::SetUpCommandLine(command_line);
-    feature_list_.InitAndEnableFeature(chrome_pdf::features::kPDFAnnotations);
-  }
-
-  base::test::ScopedFeatureList feature_list_;
-};
-
 // Disabled because it's flaky.
 // See the issue for details: https://crbug.com/826055.
 #if defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER) || \
@@ -714,102 +656,118 @@
                          PDFExtensionLoadTest,
                          testing::Range(0, kNumberLoadTestParts));
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Basic) {
+class PDFExtensionJSTest : public PDFExtensionTest {
+ public:
+  ~PDFExtensionJSTest() override = default;
+
+ protected:
+  // Runs the extensions test at chrome/test/data/pdf/<filename> on the PDF file
+  // at chrome/test/data/pdf/<pdf_filename>, where |filename| is loaded as a JS
+  // module.
+  void RunTestsInJsModule(const std::string& filename,
+                          const std::string& pdf_filename) {
+    extensions::ResultCatcher catcher;
+
+    GURL url(embedded_test_server()->GetURL("/pdf/" + pdf_filename));
+
+    // It should be good enough to just navigate to the URL. But loading up the
+    // BrowserPluginGuest seems to happen asynchronously as there was flakiness
+    // being seen due to the BrowserPluginGuest not being available yet (see
+    // crbug.com/498077). So instead use LoadPdf() which ensures that the PDF is
+    // loaded before continuing.
+    WebContents* guest_contents = LoadPdfGetGuestContents(url);
+    ASSERT_TRUE(guest_contents);
+
+    constexpr char kModuleLoaderTemplate[] =
+        R"(var s = document.createElement('script');
+           s.type = 'module';
+           s.src = '_test_resources/%s';
+           document.body.appendChild(s);)";
+
+    ASSERT_TRUE(content::ExecuteScript(
+        guest_contents,
+        base::StringPrintf(kModuleLoaderTemplate, filename.c_str())));
+
+    if (!catcher.GetNextResult())
+      FAIL() << catcher.message();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Basic) {
   RunTestsInJsModule("basic_test.js", "test.pdf");
 
   // Ensure it loaded in a PPAPI process.
   EXPECT_EQ(1, CountPDFProcesses());
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, BasicPlugin) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, BasicPlugin) {
   RunTestsInJsModule("basic_plugin_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Viewport) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Viewport) {
   RunTestsInJsModule("viewport_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Layout3) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Layout3) {
   RunTestsInJsModule("layout_test.js", "test-layout3.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Layout4) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Layout4) {
   RunTestsInJsModule("layout_test.js", "test-layout4.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Bookmark) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Bookmark) {
   RunTestsInJsModule("bookmarks_test.js", "test-bookmarks-with-zoom.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Navigator) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Navigator) {
   RunTestsInJsModule("navigator_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ParamsParser) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ParamsParser) {
   RunTestsInJsModule("params_parser_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ZoomManager) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ZoomManager) {
   RunTestsInJsModule("zoom_manager_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, GestureDetector) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, GestureDetector) {
   RunTestsInJsModule("gesture_detector_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TouchHandling) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, TouchHandling) {
   RunTestsInJsModule("touch_handling_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Elements) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Elements) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("material_elements_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ToolbarManager) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ToolbarManager) {
   RunTestsInJsModule("toolbar_manager_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Title) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Title) {
   RunTestsInJsModule("title_test.js", "test-title.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, WhitespaceTitle) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, WhitespaceTitle) {
   RunTestsInJsModule("whitespace_title_test.js", "test-whitespace-title.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Beep) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Beep) {
   RunTestsInJsModule("beep_test.js", "test-beep.pdf");
 }
 
-#if defined(OS_CHROMEOS)
-// TODO(https://crbug.com/920684): Test times out.
-#if defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER) || \
-    defined(ADDRESS_SANITIZER) || defined(_DEBUG)
-#define MAYBE_AnnotationsFeatureEnabled DISABLED_AnnotationsFeatureEnabled
-#else
-#define MAYBE_AnnotationsFeatureEnabled AnnotationsFeatureEnabled
-#endif
-IN_PROC_BROWSER_TEST_F(PDFAnnotationsTest, MAYBE_AnnotationsFeatureEnabled) {
-  RunTestsInJsModule("annotations_feature_enabled_test.js", "test.pdf");
-}
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, AnnotationsFeatureDisabled) {
-  RunTestsInJsModule("annotations_feature_disabled_test.js", "test.pdf");
-}
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Printing) {
-  RunTestsInJsModule("printing_icon_test.js", "test.pdf");
-}
-#endif
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TwoUpViewFeature) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, TwoUpViewFeature) {
   RunTestsInJsModule("two_up_view_feature_test.js", "test.pdf");
 }
 
 // TODO(tsepez): See https://crbug.com/696650.
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, DISABLED_NoBeep) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, DISABLED_NoBeep) {
   // Block the exact query from pdf/main.js while still allowing enough
   // JavaScript to run in the extension for this test harness to complete
   // its work.
@@ -824,14 +782,98 @@
   RunTestsInJsModule("nobeep_test.js", "test-beep.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PageChange) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, PageChange) {
   RunTestsInJsModule("page_change_test.js", "test-bookmarks.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Metrics) {
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Metrics) {
   RunTestsInJsModule("metrics_test.js", "test.pdf");
 }
 
+// Test that if the plugin tries to load a URL that redirects then it will fail
+// to load. This is to avoid the source origin of the document changing during
+// the redirect, which can have security implications. https://crbug.com/653749.
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, RedirectsFailInPlugin) {
+  RunTestsInJsModule("redirects_fail_test.js", "test.pdf");
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, AnnotationsFeatureDisabled) {
+  RunTestsInJsModule("annotations_feature_disabled_test.js", "test.pdf");
+}
+
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Printing) {
+  RunTestsInJsModule("printing_icon_test.js", "test.pdf");
+}
+
+class PDFAnnotationsJSTest : public PDFExtensionJSTest {
+ public:
+  ~PDFAnnotationsJSTest() override = default;
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PDFExtensionTest::SetUpCommandLine(command_line);
+    feature_list_.InitAndEnableFeature(chrome_pdf::features::kPDFAnnotations);
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// TODO(https://crbug.com/920684): Test times out.
+#if defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER) || \
+    defined(ADDRESS_SANITIZER) || defined(_DEBUG)
+#define MAYBE_AnnotationsFeatureEnabled DISABLED_AnnotationsFeatureEnabled
+#else
+#define MAYBE_AnnotationsFeatureEnabled AnnotationsFeatureEnabled
+#endif
+IN_PROC_BROWSER_TEST_F(PDFAnnotationsJSTest, MAYBE_AnnotationsFeatureEnabled) {
+  RunTestsInJsModule("annotations_feature_enabled_test.js", "test.pdf");
+}
+#endif  // defined(OS_CHROMEOS)
+
+// Service worker tests are regression tests for
+// https://crbug.com/916514.
+class PDFExtensionServiceWorkerJSTest : public PDFExtensionJSTest {
+ public:
+  ~PDFExtensionServiceWorkerJSTest() override = default;
+
+ protected:
+  // Installs the specified service worker and tests navigating to a PDF in its
+  // scope.
+  void RunServiceWorkerTest(const std::string& worker_path) {
+    // Install the service worker.
+    ui_test_utils::NavigateToURL(
+        browser(), embedded_test_server()->GetURL(
+                       "/service_worker/create_service_worker.html"));
+    EXPECT_EQ("DONE",
+              EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                     "register('" + worker_path + "', '/pdf');"));
+
+    // Navigate to a PDF in the service worker's scope. It should load.
+    RunTestsInJsModule("basic_test.js", "test.pdf");
+    // Ensure it loaded in a PPAPI process.
+    EXPECT_EQ(1, CountPDFProcesses());
+  }
+};
+
+// Test navigating to a PDF in the scope of a service worker with no fetch event
+// handler.
+IN_PROC_BROWSER_TEST_F(PDFExtensionServiceWorkerJSTest, NoFetchHandler) {
+  RunServiceWorkerTest("empty.js");
+}
+
+// Test navigating to a PDF when a service worker intercepts the request and
+// then falls back to network by not calling FetchEvent.respondWith().
+IN_PROC_BROWSER_TEST_F(PDFExtensionServiceWorkerJSTest, NetworkFallback) {
+  RunServiceWorkerTest("network_fallback_worker.js");
+}
+
+// Test navigating to a PDF when a service worker intercepts the request and
+// provides a response.
+IN_PROC_BROWSER_TEST_F(PDFExtensionServiceWorkerJSTest, Interception) {
+  RunServiceWorkerTest("respond_with_fetch_worker.js");
+}
+
 // Ensure that the internal PDF plugin application/x-google-chrome-pdf won't be
 // loaded if it's not loaded in the chrome extension page.
 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureInternalPluginDisabled) {
@@ -1284,13 +1326,6 @@
 }
 #endif
 
-// Test that if the plugin tries to load a URL that redirects then it will fail
-// to load. This is to avoid the source origin of the document changing during
-// the redirect, which can have security implications. https://crbug.com/653749.
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, RedirectsFailInPlugin) {
-  RunTestsInJsModule("redirects_fail_test.js", "test.pdf");
-}
-
 // Test that even if a different tab is selected when a navigation occurs,
 // the correct tab still gets navigated (see crbug.com/672563).
 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, NavigationOnCorrectTab) {
@@ -1812,7 +1847,7 @@
   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
 }
 
-// TODO: test is flaky. https://crbug.com/897801
+// TODO(crbug.com/897801): test is flaky.
 IN_PROC_BROWSER_TEST_F(PDFExtensionClipboardTest,
                        DISABLED_IndividualShiftLeftArrowPresses) {
   LoadTestComboBoxPdfGetGuestContents();
@@ -2176,27 +2211,6 @@
   EXPECT_EQ(inner, outer);
 }
 
-// Service worker tests are regression tests for
-// https://crbug.com/916514.
-
-// Test navigating to a PDF in the scope of a service worker with no fetch event
-// handler.
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ServiceWorkerNoFetchHandler) {
-  RunServiceWorkerTest("empty.js");
-}
-
-// Test navigating to a PDF when a service worker intercepts the request and
-// then falls back to network by not calling FetchEvent.respondWith().
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ServiceWorkerNetworkFallback) {
-  RunServiceWorkerTest("network_fallback_worker.js");
-}
-
-// Test navigating to a PDF when a service worker intercepts the request and
-// provides a response.
-IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ServiceWorkerInterception) {
-  RunServiceWorkerTest("respond_with_fetch_worker.js");
-}
-
 // A helper for waiting for the first request for |url_to_intercept|.
 class RequestWaiter {
  public:
@@ -2333,6 +2347,32 @@
   ASSERT_TRUE(LoadPdf(test_pdf_url));
 }
 
+IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Metrics) {
+  base::HistogramTester histograms;
+  base::UserActionTester actions;
+
+  GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/combobox_form.pdf"));
+  WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
+  ASSERT_TRUE(guest_contents);
+
+  SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+  // Histograms.
+  // Duplicating some constants to avoid reaching into pdf/ internals.
+  constexpr int kLoadedDocument = 0;
+  constexpr int kAcroForm = 1;
+  histograms.ExpectUniqueSample("PDF.DocumentFeature", kLoadedDocument, 1);
+  histograms.ExpectUniqueSample("PDF.FormType", kAcroForm, 1);
+  histograms.ExpectUniqueSample("PDF.IsTagged", 0, 1);
+  histograms.ExpectUniqueSample("PDF.HasAttachment", 0, 1);
+
+  // Custom histograms.
+  histograms.ExpectUniqueSample("PDF.PageCount", 1, 1);
+
+  // User actions.
+  EXPECT_EQ(1, actions.GetActionCount("PDF.LoadSuccess"));
+}
+
 // This test suite does a simple text-extraction based on the accessibility
 // internals, breaking lines & paragraphs where appropriate.  Unlike
 // TreeDumpTests, this allows us to verify the kNextOnLine and kPreviousOnLine
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 9e1d5a33a..2844148 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -354,9 +354,9 @@
 #endif
 
 #if defined(OS_MACOSX)
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
 #include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
 #include "chrome/browser/ui/cocoa/confirm_quit.h"
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
 #endif
 
 #if defined(OS_WIN)
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing_test.js
index 089ee94..1a08c53 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing_test.js
@@ -1311,6 +1311,7 @@
       const mockFeedback = this.createMockFeedback();
       this.runWithLoadedTree(
           `
+    <p>start</p>
     <div contenteditable role="textbox">
       <p>hello</p>
     </div>
diff --git a/chrome/browser/resources/media/BUILD.gn b/chrome/browser/resources/media/BUILD.gn
index cbdacbaa..2fef84ad 100644
--- a/chrome/browser/resources/media/BUILD.gn
+++ b/chrome/browser/resources/media/BUILD.gn
@@ -21,13 +21,36 @@
 }
 
 js_type_check("closure_compile") {
-  deps = [ ":media_feeds" ]
+  deps = [
+    ":media_data_table",
+    ":media_feeds",
+    ":media_history",
+  ]
+}
+
+js_library("media_history") {
+  deps = [
+    ":media_data_table",
+    "//chrome/browser/media:mojo_bindings_js_library_for_compile",
+    "//ui/webui/resources/js:assert",
+    "//ui/webui/resources/js:util",
+    "//ui/webui/resources/js/cr/ui:tabs",
+  ]
 }
 
 js_library("media_feeds") {
   deps = [
+    ":media_data_table",
     "//chrome/browser/media/feeds:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:util",
   ]
 }
+
+js_library("media_data_table") {
+  deps = [
+    "//ui/webui/resources/js:assert",
+    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:util",
+  ]
+}
diff --git a/chrome/browser/resources/media/media_data_table.js b/chrome/browser/resources/media/media_data_table.js
new file mode 100644
index 0000000..758ace61
--- /dev/null
+++ b/chrome/browser/resources/media/media_data_table.js
@@ -0,0 +1,124 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+cr.define('cr.ui', function() {
+  /**
+   * TODO(beccahughes): Description
+   */
+  /* #export */ class MediaDataTable {
+    /**
+     * @param {HTMLElement} table
+     * @param {!cr.ui.MediaDataTableDelegate} delegate
+     */
+    constructor(table, delegate) {
+      /** @private {HTMLElement} */
+      this.table_ = table;
+
+      /** @private {Array<Object>} */
+      this.data_ = [];
+
+      /** @private {cr.ui.MediaDataTableDelegate} */
+      this.delegate_ = delegate;
+
+      // Set table header sort handlers.
+      const headers = this.table_.querySelectorAll('th[sort-key]');
+      headers.forEach(header => {
+        header.addEventListener('click', this.handleSortClick_.bind(this));
+      });
+    }
+
+    handleSortClick_(e) {
+      if (e.target.classList.contains('sort-column')) {
+        e.target.toggleAttribute('sort-reverse');
+      } else {
+        document.querySelector('.sort-column').classList.remove('sort-column');
+        e.target.classList.add('sort-column');
+      }
+
+      this.render();
+    }
+
+    render() {
+      // Find the body of the table and clear it.
+      const body = this.table_.querySelectorAll('tbody')[0];
+      body.innerHTML = '';
+
+      // Get the sort key from the columns to determine which data should be in
+      // which column.
+      const headerCells = Array.from(this.table_.querySelectorAll('thead th'));
+      const dataAndSortKeys = headerCells.map((e) => {
+        return e.getAttribute('sort-key') ? e.getAttribute('sort-key') :
+                                            e.getAttribute('data-key');
+      });
+
+      const currentSortCol = this.table_.querySelectorAll('.sort-column')[0];
+      const currentSortKey = currentSortCol.getAttribute('sort-key');
+      const currentSortReverse = currentSortCol.hasAttribute('sort-reverse');
+
+      // Sort the data based on the current sort key.
+      this.data_.sort((a, b) => {
+        return (currentSortReverse ? -1 : 1) *
+            this.delegate_.compareTableItem(currentSortKey, a, b);
+      });
+
+      // Generate the table rows.
+      this.data_.forEach((dataRow) => {
+        const tr = document.createElement('tr');
+        body.appendChild(tr);
+
+        dataAndSortKeys.forEach((key) => {
+          const td = document.createElement('td');
+
+          // Keys with a period denote nested objects.
+          let data = dataRow;
+          const expandedKey = key.split('.');
+          expandedKey.forEach((k) => {
+            data = data[k];
+            key = k;
+          });
+
+          this.delegate_.insertDataField(td, data, key, dataRow);
+          tr.appendChild(td);
+        });
+      });
+    }
+
+    /**
+     * @param {Array} data The data to update
+     */
+    setData(data) {
+      this.data_ = data;
+      this.render();
+    }
+  }
+
+  /** @interface */
+  /* #export */ class MediaDataTableDelegate {
+    /**
+     * Formats a field to be displayed in the data table and inserts it into the
+     * element.
+     * @param {Element} td
+     * @param {?Object} data
+     * @param {string} key
+     * @param {Object} dataRow This is the row itself in case we need extra
+     *   data to render the field.
+     */
+    insertDataField(td, data, key, dataRow) {}
+
+    /**
+     * Compares two objects based on |sortKey|.
+     * @param {string} sortKey The name of the property to sort by.
+     * @param {?Object} a The first object to compare.
+     * @param {?Object} b The second object to compare.
+     * @return {number} A negative number if |a| should be ordered
+     *     before |b|, a positive number otherwise.
+     */
+    compareTableItem(sortKey, a, b) {}
+  }
+
+  // #cr_define_end
+  return {MediaDataTable, MediaDataTableDelegate};
+});
diff --git a/chrome/browser/resources/media/media_feeds.html b/chrome/browser/resources/media/media_feeds.html
index 448f9be..1083adcf 100644
--- a/chrome/browser/resources/media/media_feeds.html
+++ b/chrome/browser/resources/media/media_feeds.html
@@ -17,6 +17,10 @@
   <script src="chrome/browser/media/feeds/media_feeds_store.mojom-lite.js">
   </script>
 
+  <script src="chrome://resources/js/cr.js"></script>
+  <script src="chrome://resources/js/cr/ui.js"></script>
+
+  <script src="chrome://media-feeds/media-data-table.js"></script>
   <script src="chrome://media-feeds/media-feeds.js"></script>
   <style>
     body {
@@ -79,7 +83,7 @@
 <body>
   <h1>Media Feeds</h1>
   <button id="copy-all-to-clipboard">Copy all to clipboard</button>
-  <table>
+  <table id="feeds-table">
     <thead>
       <tr id="feed-table-header">
         <th sort-key="id" class="sort-column" sort-reverse>
@@ -118,8 +122,10 @@
         <th sort-key="lastFetchContentTypes">
           Last Fetch Content Types
         </th>
-        <th>
+        <th data-key="logos">
           Logos
+        <th data-key="actions">
+          Actions
         </th>
       </tr>
     </thead>
@@ -127,22 +133,71 @@
     </tbody>
   </table>
 
-  <template id="datarow">
-    <tr>
-      <td class="id-cell"></td>
-      <td class="url-cell"></td>
-      <td></td>
-      <td class="last-discovery-time-cell"></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-      <td></td>
-    </tr>
-  </template>
+  <div id="feed-content" style="display:none;">
+    <hr>
+    <h2>Feed Contents: <span id="current-feed"></span></h2>
+    <table id="feed-items-table">
+      <thead>
+        <tr>
+          <th sort-key="type" class="sort-column" sort-reverse>
+            Type
+          </th>
+          <th sort-key="name">
+            Name
+          </th>
+          <th sort-key="author">
+            Author
+          </th>
+          <th sort-key="datePublished">
+            Date Published
+          </th>
+          <th sort-key="isFamilyFriendly">
+            Family Friendly
+          </th>
+          <th sort-key="actionStatus">
+            Action Status
+          </th>
+          <th sort-key="action.url">
+            Action URL
+          </th>
+          <th sort-key="action.startTime">
+            Action Start Time (secs)
+          </th>
+          <th sort-key="interactionCounters">
+            Interaction Counters
+          </th>
+          <th sort-key="contentRatings">
+            Content Ratings
+          </th>
+          <th sort-key="genre">
+            Genre
+          </th>
+          <th sort-key="live">
+            Live Details
+          </th>
+          <th sort-key="tvEpisode">
+            TV Episode
+          </th>
+          <th sort-key="playNextCandidate">
+            Play Next Candidate
+          </th>
+          <th sort-key="identifiers">
+            Identifiers
+          </th>
+          <th sort-key="shownCount">
+            Shown Count
+          </th>
+          <th sort-key="clicked">
+            Clicked
+          </th>
+          <th sort-key="images">
+            Images
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+      </tbody>
+    </table>
+  </div>
 </body>
 </html>
diff --git a/chrome/browser/resources/media/media_feeds.js b/chrome/browser/resources/media/media_feeds.js
index fe5151e6..530ab9d2 100644
--- a/chrome/browser/resources/media/media_feeds.js
+++ b/chrome/browser/resources/media/media_feeds.js
@@ -2,108 +2,335 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(https://crbug.com/1059352): Factor out the sortable table.
-
 'use strict';
 
 // Allow a function to be provided by tests, which will be called when
 // the page has been populated with media feeds details.
-const pageIsPopulatedResolver = new PromiseResolver();
+const mediaFeedsPageIsPopulatedResolver = new PromiseResolver();
 function whenPageIsPopulatedForTest() {
-  return pageIsPopulatedResolver.promise;
+  return mediaFeedsPageIsPopulatedResolver.promise;
+}
+
+const mediaFeedItemsPageIsPopulatedResolver = new PromiseResolver();
+function whenFeedTableIsPopulatedForTest() {
+  return mediaFeedItemsPageIsPopulatedResolver.promise;
 }
 
 (function() {
 
-let detailsProvider = null;
-let info = null;
-let feedTableBody = null;
-let sortReverse = true;
-let sortKey = 'id';
+let delegate = null;
+let feedsTable = null;
+let feedItemsTable = null;
+let store = null;
+
+/** @implements {cr.ui.MediaDataTableDelegate} */
+class MediaFeedsTableDelegate {
+  /**
+   * Formats a field to be displayed in the data table and inserts it into the
+   * element.
+   * @param {Element} td
+   * @param {?Object} data
+   * @param {string} key
+   * @param {Object} dataRow
+   */
+  insertDataField(td, data, key, dataRow) {
+    if (key == 'actions') {
+      const a = document.createElement('a');
+      a.href = '#feed-content';
+      a.textContent = 'Show Contents';
+      td.appendChild(a);
+
+      a.addEventListener('click', () => {
+        store.getItemsForMediaFeed(dataRow.id).then(response => {
+          feedItemsTable.setData(response.items);
+
+          // Show the feed items section.
+          $('current-feed').textContent = dataRow.url.url;
+          $('feed-content').style.display = 'block';
+
+          mediaFeedItemsPageIsPopulatedResolver.resolve();
+        });
+      });
+    }
+
+    if (data === undefined || data === null) {
+      return;
+    }
+
+    if (key === 'url') {
+      // Format a mojo GURL.
+      td.textContent = data.url;
+    } else if (
+        key === 'lastDiscoveryTime' || key === 'lastFetchTime' ||
+        key === 'cacheExpiryTime' || key === 'datePublished') {
+      // Format a mojo time.
+      td.textContent =
+          convertMojoTimeToJS(/** @type {mojoBase.mojom.Time} */ (data))
+              .toString();
+    } else if (key === 'userStatus') {
+      // Format a FeedUserStatus.
+      if (data == mediaFeeds.mojom.FeedUserStatus.kAuto) {
+        td.textContent = 'Auto';
+      } else if (data == mediaFeeds.mojom.FeedUserStatus.kDisabled) {
+        td.textContent = 'Disabled';
+      }
+    } else if (key === 'lastFetchResult') {
+      // Format a FetchResult.
+      if (data == mediaFeeds.mojom.FetchResult.kNone) {
+        td.textContent = 'None';
+      } else if (data == mediaFeeds.mojom.FetchResult.kSuccess) {
+        td.textContent = 'Success';
+      } else if (data == mediaFeeds.mojom.FetchResult.kFailedBackendError) {
+        td.textContent = 'Failed (Backend Error)';
+      } else if (data == mediaFeeds.mojom.FetchResult.kFailedNetworkError) {
+        td.textContent = 'Failed (Network Error)';
+      }
+    } else if (key === 'lastFetchContentTypes') {
+      // Format a MediaFeedItemType.
+      const contentTypes = [];
+      const itemType = parseInt(data, 10);
+
+      if (itemType & mediaFeeds.mojom.MediaFeedItemType.kVideo) {
+        contentTypes.push('Video');
+      } else if (itemType & mediaFeeds.mojom.MediaFeedItemType.kTVSeries) {
+        contentTypes.push('TV Series');
+      } else if (itemType & mediaFeeds.mojom.MediaFeedItemType.kMovie) {
+        contentTypes.push('Movie');
+      }
+
+      td.textContent =
+          contentTypes.length === 0 ? 'None' : contentTypes.join(',');
+    } else if (key === 'logos' || key === 'images') {
+      // Format an array of mojo media images.
+      data.forEach((image) => {
+        const a = document.createElement('a');
+        a.href = image.src.url;
+        a.textContent = image.src.url;
+        a.target = '_blank';
+        td.appendChild(a);
+        td.appendChild(document.createElement('br'));
+      });
+    } else if (key == 'type') {
+      // Format a MediaFeedItemType.
+      switch (parseInt(data, 10)) {
+        case mediaFeeds.mojom.MediaFeedItemType.kVideo:
+          td.textContent = 'Video';
+          break;
+        case mediaFeeds.mojom.MediaFeedItemType.kTVSeries:
+          td.textContent = 'TV Series';
+          break;
+        case mediaFeeds.mojom.MediaFeedItemType.kMovie:
+          td.textContent = 'Movie';
+          break;
+      }
+    } else if (key == 'isFamilyFriendly' || key == 'clicked') {
+      // Format a boolean.
+      td.textContent = data ? 'Yes' : 'No';
+    } else if (key == 'actionStatus') {
+      // Format a MediaFeedItemActionStatus.
+      switch (parseInt(data, 10)) {
+        case mediaFeeds.mojom.MediaFeedItemActionStatus.kUnknown:
+          td.textContent = 'Unknown';
+          break;
+        case mediaFeeds.mojom.MediaFeedItemActionStatus.kActive:
+          td.textContent = 'Active';
+          break;
+        case mediaFeeds.mojom.MediaFeedItemActionStatus.kPotential:
+          td.textContent = 'Potential';
+          break;
+        case mediaFeeds.mojom.MediaFeedItemActionStatus.kCompleted:
+          td.textContent = 'Completed';
+          break;
+      }
+    } else if (key == 'startTime') {
+      // Format a start time.
+      td.textContent =
+          timeDeltaToSeconds(/** @type {mojoBase.mojom.TimeDelta} */ (data));
+    } else if (key == 'interactionCounters') {
+      // Format interaction counters.
+      const counters = [];
+
+      Object.keys(data).forEach((key) => {
+        let keyString = '';
+
+        switch (parseInt(key, 10)) {
+          case mediaFeeds.mojom.InteractionCounterType.kWatch:
+            keyString = 'Watch';
+            break;
+          case mediaFeeds.mojom.InteractionCounterType.kLike:
+            keyString = 'Like';
+            break;
+          case mediaFeeds.mojom.InteractionCounterType.kDislike:
+            keyString = 'Dislike';
+            break;
+        }
+
+        counters.push(keyString + '=' + data[key]);
+      });
+
+      td.textContent = counters.join(' ');
+    } else if (key == 'contentRatings') {
+      // Format content ratings.
+      const ratings = [];
+
+      data.forEach((rating) => {
+        ratings.push(rating.agency + ' ' + rating.value);
+      });
+
+      td.textContent = ratings.join(', ');
+    } else if (key == 'author') {
+      // Format a mojom author.
+      const a = document.createElement('a');
+      a.href = data.url;
+      a.textContent = data.name;
+      a.target = '_blank';
+      td.appendChild(a);
+    } else if (key == 'name' || key == 'genre') {
+      // Format a mojo string16.
+      td.textContent =
+          decodeString16(/** @type {mojoBase.mojom.String16} */ (data));
+    } else if (key == 'live') {
+      // Format LiveDetails.
+      td.textContent = 'Live';
+
+      if (data.startTime) {
+        td.textContent += ' ' +
+            'StartTime=' +
+            convertMojoTimeToJS(
+                /** @type {mojoBase.mojom.Time} */ (data.startTime))
+                .toString();
+      }
+
+      if (data.endTime) {
+        td.textContent += ' ' +
+            'EndTime=' +
+            convertMojoTimeToJS(
+                /** @type {mojoBase.mojom.Time} */ (data.endTime))
+                .toString();
+      }
+    } else if (key == 'tvEpisode') {
+      // Format a TV Episode.
+      td.textContent = data.name + ' EpisodeNumber=' + data.episodeNumber +
+          ' SeasonNumber=' + data.seasonNumber + ' ' +
+          formatIdentifiers(/** @type {Array<mediaFeeds.mojom.Identifier>} */ (
+              data.identifiers));
+    } else if (key == 'playNextCandidate') {
+      // Format a Play Next Candidate.
+      td.textContent = data.name + ' EpisodeNumber=' + data.episodeNumber +
+          ' SeasonNumber=' + data.seasonNumber + ' ' +
+          formatIdentifiers(
+                           /** @type {Array<mediaFeeds.mojom.Identifier>} */ (
+                               data.identifiers)) +
+          ' ActionURL=' + data.action.url.url;
+
+      if (data.action.startTime) {
+        td.textContent +=
+            ' ActionStartTimeSecs=' + timeDeltaToSeconds(data.action.startTime);
+      }
+
+      td.textContent += ' DurationSecs=' + timeDeltaToSeconds(data.duration);
+    } else if (key == 'identifiers') {
+      // Format identifiers.
+      td.textContent = formatIdentifiers(
+          /** @type {Array<mediaFeeds.mojom.Identifier>} */ (data));
+    } else {
+      td.textContent = data;
+    }
+  }
+
+  /**
+   * Compares two objects based on |sortKey|.
+   * @param {string} sortKey The name of the property to sort by.
+   * @param {?Object} a The first object to compare.
+   * @param {?Object} b The second object to compare.
+   * @return {number} A negative number if |a| should be ordered
+   *     before |b|, a positive number otherwise.
+   */
+  compareTableItem(sortKey, a, b) {
+    const val1 = a[sortKey];
+    const val2 = b[sortKey];
+
+    if (sortKey === 'url') {
+      return val1.url > val2.url ? 1 : -1;
+    } else if (
+        sortKey === 'id' || sortKey === 'displayName' ||
+        sortKey === 'userStatus' || sortKey === 'lastFetchResult' ||
+        sortKey === 'fetchFailedCount' || sortKey === 'lastFetchItemCount' ||
+        sortKey === 'lastFetchPlayNextCount' ||
+        sortKey === 'lastFetchContentTypes') {
+      return val1 > val2 ? 1 : -1;
+    } else if (
+        sortKey === 'lastDiscoveryTime' || sortKey === 'lastFetchTime' ||
+        sortKey === 'cacheExpiryTime') {
+      return val1.internalValue > val2.internalValue ? 1 : -1;
+    }
+
+    assertNotReached('Unsupported sort key: ' + sortKey);
+    return 0;
+  }
+}
 
 /**
- * Creates a single row in the feeds table.
- * @param {!mediaFeeds.mojom.MediaFeed} rowInfo The info to create the row.
- * @return {!Node}
+ * Convert a time delta to seconds.
+ * @param {mojoBase.mojom.TimeDelta} timeDelta
+ * @returns {number}
  */
-function createRow(rowInfo) {
-  const template = $('datarow');
-  const td = template.content.querySelectorAll('td');
+function timeDeltaToSeconds(timeDelta) {
+  return timeDelta.microseconds / 1000 / 1000;
+}
 
-  td[0].textContent = rowInfo.id;
-  td[1].textContent = rowInfo.url.url;
-  td[2].textContent = rowInfo.displayName;
-  td[3].textContent = convertMojoTimeToJS(rowInfo.lastDiscoveryTime).toString();
+/**
+ * Formats an array of identifiers for display.
+ * @param {Array<mediaFeeds.mojom.Identifier>} mojoIdentifiers
+ * @returns {string}
+ */
+function formatIdentifiers(mojoIdentifiers) {
+  const identifiers = [];
 
-  if (rowInfo.lastFetchTime != null) {
-    td[4].textContent = convertMojoTimeToJS(rowInfo.lastFetchTime).toString();
-  }
+  mojoIdentifiers.forEach((identifier) => {
+    let keyString = '';
 
-  if (rowInfo.userStatus == mediaFeeds.mojom.FeedUserStatus.kAuto) {
-    td[5].textContent = 'Auto';
-  } else if (rowInfo.userStatus == mediaFeeds.mojom.FeedUserStatus.kDisabled) {
-    td[5].textContent = 'Disabled';
-  }
+    switch (identifier.type) {
+      case mediaFeeds.mojom.Identifier_Type.kTMSRootId:
+        keyString = 'TMSRootId';
+        break;
+      case mediaFeeds.mojom.Identifier_Type.kTMSId:
+        keyString = 'TMSId';
+        break;
+      case mediaFeeds.mojom.Identifier_Type.kPartnerId:
+        keyString = 'PartnerId';
+        break;
+    }
 
-  if (rowInfo.lastFetchResult == mediaFeeds.mojom.FetchResult.kNone) {
-    td[6].textContent = 'None';
-  } else if (rowInfo.lastFetchResult == mediaFeeds.mojom.FetchResult.kSuccess) {
-    td[6].textContent = 'Success';
-  } else if (
-      rowInfo.lastFetchResult ==
-      mediaFeeds.mojom.FetchResult.kFailedBackendError) {
-    td[6].textContent = 'Failed (Backend Error)';
-  } else if (
-      rowInfo.lastFetchResult ==
-      mediaFeeds.mojom.FetchResult.kFailedNetworkError) {
-    td[6].textContent = 'Failed (Network Error)';
-  }
-
-  td[7].textContent = rowInfo.fetchFailedCount;
-
-  if (rowInfo.cacheExpiryTime != null) {
-    td[8].textContent = convertMojoTimeToJS(rowInfo.cacheExpiryTime).toString();
-  }
-
-  td[9].textContent = rowInfo.lastFetchItemCount;
-  td[10].textContent = rowInfo.lastFetchPlayNextCount;
-
-  const contentTypes = [];
-  if (rowInfo.lastFetchContentTypes &
-      mediaFeeds.mojom.MediaFeedItemType.kVideo) {
-    contentTypes.push('Video');
-  } else if (
-      rowInfo.lastFetchContentTypes &
-      mediaFeeds.mojom.MediaFeedItemType.kTVSeries) {
-    contentTypes.push('TV Series');
-  } else if (
-      rowInfo.lastFetchContentTypes &
-      mediaFeeds.mojom.MediaFeedItemType.kMovie) {
-    contentTypes.push('Movie');
-  }
-
-  td[11].textContent =
-      contentTypes.length === 0 ? 'None' : contentTypes.join(',');
-
-  // Format an array of mojo media images.
-  rowInfo.logos.forEach((image) => {
-    const a = document.createElement('a');
-    a.href = image.src.url;
-    a.textContent = image.src.url;
-    a.target = '_blank';
-    td[12].appendChild(a);
-    td[12].appendChild(document.createElement('br'));
+    identifiers.push(keyString + '=' + identifier.value);
   });
 
-  return document.importNode(template.content, true);
+  return identifiers.join(' ');
+}
+
+/**
+ * Parses utf16 coded string.
+ * @param {?mojoBase.mojom.String16} arr
+ * @return {string}
+ */
+function decodeString16(arr) {
+  if (arr == null) {
+    return '';
+  }
+
+  return arr.data.map(ch => String.fromCodePoint(ch)).join('');
 }
 
 /**
  * Converts a mojo time to a JS time.
- * @param {!mojoBase.mojom.Time} mojoTime
+ * @param {mojoBase.mojom.Time} mojoTime
  * @return {Date}
  */
 function convertMojoTimeToJS(mojoTime) {
+  if (mojoTime === null) {
+    return new Date();
+  }
+
   // The new Date().getTime() returns the number of milliseconds since the
   // UNIX epoch (1970-01-01 00::00:00 UTC), while |internalValue| of the
   // device.mojom.Geoposition represents the value of microseconds since the
@@ -119,108 +346,23 @@
 }
 
 /**
- * Remove all rows from the feeds table.
- */
-function clearTable() {
-  feedTableBody.innerHTML = '';
-}
-
-/**
- * Sort the feed info based on |sortKey| and |sortReverse|.
- */
-function sortInfo() {
-  info.sort((a, b) => {
-    return (sortReverse ? -1 : 1) * compareTableItem(sortKey, a, b);
-  });
-}
-
-/**
- * Compares two MediaFeed objects based on |sortKey|.
- * @param {string} sortKey The name of the property to sort by.
- * @param {mediaFeeds.mojom.MediaFeed} a First object to compare.
- * @param {mediaFeeds.mojom.MediaFeed} b Second object to compare.
- * @return {number|boolean} A negative number if |a| should be ordered before
- *     |b|, a positive number otherwise.
- */
-function compareTableItem(sortKey, a, b) {
-  if (sortKey == 'url') {
-    return a.url.url > b.url.url ? 1 : -1;
-  } else if (sortKey == 'id') {
-    return a.id > b.id;
-  } else if (sortKey == 'lastDiscoveryTime') {
-    return (
-        a.lastDiscoveryTime.internalValue > b.lastDiscoveryTime.internalValue);
-  } else if (sortKey == 'displayName') {
-    return a.displayName > b.displayName;
-  } else if (sortKey == 'userStatus') {
-    return a.userStatus > b.userStatus;
-  } else if (sortKey == 'lastFetchResult') {
-    return a.lastFetchResult > b.lastFetchResult;
-  } else if (sortKey == 'fetchFailedCount') {
-    return a.fetchFailedCount > b.fetchFailedCount;
-  } else if (sortKey == 'cacheExpiryTime') {
-    return a.cacheExpiryTime > b.cacheExpiryTime;
-  } else if (sortKey == 'lastFetchItemCount') {
-    return a.lastFetchItemCount > b.lastFetchItemCount;
-  } else if (sortKey == 'lastFetchPlayNextCount') {
-    return a.lastFetchPlayNextCount > b.lastFetchPlayNextCount;
-  } else if (sortKey == 'lastFetchContentTypes') {
-    return a.lastFetchContentTypes > b.lastFetchContentTypes;
-  }
-
-  assertNotReached('Unsupported sort key: ' + sortKey);
-  return 0;
-}
-
-/**
- * Regenerates the feed table from |info|.
- */
-function renderTable() {
-  clearTable();
-  sortInfo();
-  info.forEach(rowInfo => feedTableBody.appendChild(createRow(rowInfo)));
-}
-
-/**
  * Retrieve feed info and render the feed table.
  */
-function updateFeedTable() {
-  detailsProvider.getMediaFeeds().then(response => {
-    info = response.feeds;
-    renderTable();
-    pageIsPopulatedResolver.resolve();
+function updateFeedsTable() {
+  store.getMediaFeeds().then(response => {
+    feedsTable.setData(response.feeds);
+    mediaFeedsPageIsPopulatedResolver.resolve();
   });
 }
 
 document.addEventListener('DOMContentLoaded', () => {
-  detailsProvider = mediaFeeds.mojom.MediaFeedsStore.getRemote();
+  store = mediaFeeds.mojom.MediaFeedsStore.getRemote();
 
-  feedTableBody = $('feed-table-body');
-  updateFeedTable();
+  delegate = new MediaFeedsTableDelegate();
+  feedsTable = new cr.ui.MediaDataTable($('feeds-table'), delegate);
+  feedItemsTable = new cr.ui.MediaDataTable($('feed-items-table'), delegate);
 
-  // Set table header sort handlers.
-  const feedTableHeader = $('feed-table-header');
-  const headers = feedTableHeader.children;
-  for (let i = 0; i < headers.length; i++) {
-    headers[i].addEventListener('click', (e) => {
-      const newSortKey = e.target.getAttribute('sort-key');
-      if (sortKey == newSortKey) {
-        sortReverse = !sortReverse;
-      } else {
-        sortKey = newSortKey;
-        sortReverse = false;
-      }
-      const oldSortColumn = document.querySelector('.sort-column');
-      oldSortColumn.classList.remove('sort-column');
-      e.target.classList.add('sort-column');
-      if (sortReverse) {
-        e.target.setAttribute('sort-reverse', '');
-      } else {
-        e.target.removeAttribute('sort-reverse');
-      }
-      renderTable();
-    });
-  }
+  updateFeedsTable();
 
   // Add handler to 'copy all to clipboard' button
   const copyAllToClipboardButton = $('copy-all-to-clipboard');
diff --git a/chrome/browser/resources/media/media_history.html b/chrome/browser/resources/media/media_history.html
index 50f3a3da..894eb8a 100644
--- a/chrome/browser/resources/media/media_history.html
+++ b/chrome/browser/resources/media/media_history.html
@@ -24,6 +24,7 @@
   <script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
   <script src="chrome://resources/js/cr/ui/tabs.js"></script>
 
+  <script src="chrome://media-history/media-data-table.js"></script>
   <script src="chrome://media-history/media-history.js"></script>
   <style>
     html,
diff --git a/chrome/browser/resources/media/media_history.js b/chrome/browser/resources/media/media_history.js
index 063b87a..30c66de 100644
--- a/chrome/browser/resources/media/media_history.js
+++ b/chrome/browser/resources/media/media_history.js
@@ -6,9 +6,9 @@
 
 // Allow a function to be provided by tests, which will be called when
 // the page has been populated.
-const pageIsPopulatedResolver = new PromiseResolver();
+const mediaHistoryPageIsPopulatedResolver = new PromiseResolver();
 function whenPageIsPopulatedForTest() {
-  return pageIsPopulatedResolver.promise;
+  return mediaHistoryPageIsPopulatedResolver.promise;
 }
 
 (function() {
@@ -18,12 +18,13 @@
 let originsTable = null;
 let playbacksTable = null;
 let sessionsTable = null;
+let delegate = null;
 
 /**
  * Creates a single row in the stats table.
  * @param {string} name The name of the table.
- * @param {string} count The row count of the table.
- * @return {!HTMLElement}
+ * @param {number} count The row count of the table.
+ * @return {!Node}
  */
 function createStatsRow(name, count) {
   const template = $('stats-row');
@@ -33,219 +34,130 @@
   return document.importNode(template.content, true);
 }
 
-/**
- * Compares two MediaHistoryOriginRow objects based on |sortKey|.
- * @param {string} sortKey The name of the property to sort by.
- * @param {number|mojo_base.mojom.TimeDelta|url.mojom.Origin} The first object
- *     to compare.
- * @param {number|mojo_base.mojom.TimeDelta|url.mojom.Origin} The second object
- *     to compare.
- * @return {number} A negative number if |a| should be ordered before
- *     |b|, a positive number otherwise.
- */
-function compareTableItem(sortKey, a, b) {
-  const val1 = a[sortKey];
-  const val2 = b[sortKey];
+/** @implements {cr.ui.MediaDataTableDelegate} */
+class MediaHistoryTableDelegate {
+  /**
+   * Formats a field to be displayed in the data table and inserts it into the
+   * element.
+   * @param {Element} td
+   * @param {?Object} data
+   * @param {string} key
+   */
+  insertDataField(td, data, key) {
+    if (data === undefined || data === null) {
+      return;
+    }
 
-  // Compare the hosts of the origin ignoring schemes.
-  if (sortKey == 'origin') {
-    return val1.host > val2.host ? 1 : -1;
+    if (key === 'origin') {
+      // Format a mojo origin.
+      const {scheme, host, port} = data;
+      td.textContent = new URL(`${scheme}://${host}:${port}`).origin;
+    } else if (key === 'lastUpdatedTime') {
+      // Format a JS timestamp.
+      td.textContent = data ? new Date(data).toISOString() : '';
+    } else if (
+        key === 'cachedAudioVideoWatchtime' ||
+        key === 'actualAudioVideoWatchtime' || key === 'watchtime' ||
+        key === 'duration' || key === 'position') {
+      // Format a mojo timedelta.
+      const secs = (data.microseconds / 1000000);
+      td.textContent =
+          secs.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
+    } else if (key === 'url') {
+      // Format a mojo GURL.
+      td.textContent = data.url;
+    } else if (key === 'hasAudio' || key === 'hasVideo') {
+      // Format a boolean.
+      td.textContent = data ? 'Yes' : 'No';
+    } else if (
+        key === 'title' || key === 'artist' || key === 'album' ||
+        key === 'sourceTitle') {
+      // Format a mojo string16.
+      td.textContent = decodeString16(
+          /** @type {mojoBase.mojom.String16} */ (data));
+    } else if (key === 'artwork') {
+      // Format an array of mojo media images.
+      data.forEach((image) => {
+        const a = document.createElement('a');
+        a.href = image.src.url;
+        a.textContent = image.src.url;
+        a.target = '_blank';
+        td.appendChild(a);
+        td.appendChild(document.createElement('br'));
+      });
+    } else {
+      td.textContent = data;
+    }
   }
 
-  // Compare the url property.
-  if (sortKey == 'url') {
-    return val1.url > val2.url ? 1 : -1;
+  /**
+   * Compares two objects based on |sortKey|.
+   * @param {string} sortKey The name of the property to sort by.
+   * @param {?Object} a The first object to compare.
+   * @param {?Object} b The second object to compare.
+   * @return {number} A negative number if |a| should be ordered
+   *     before |b|, a positive number otherwise.
+   */
+  compareTableItem(sortKey, a, b) {
+    const val1 = a[sortKey];
+    const val2 = b[sortKey];
+
+    // Compare the hosts of the origin ignoring schemes.
+    if (sortKey === 'origin') {
+      return val1.host > val2.host ? 1 : -1;
+    }
+
+    // Compare the url property.
+    if (sortKey === 'url') {
+      return val1.url > val2.url ? 1 : -1;
+    }
+
+    // Compare mojo_base.mojom.TimeDelta microseconds value.
+    if (sortKey === 'cachedAudioVideoWatchtime' ||
+        sortKey === 'actualAudioVideoWatchtime' || sortKey === 'watchtime' ||
+        sortKey === 'duration' || sortKey === 'position') {
+      return val1.microseconds - val2.microseconds;
+    }
+
+    if (sortKey.startsWith('metadata.')) {
+      // Keys with a period denote nested objects.
+      let nestedA = a;
+      let nestedB = b;
+      const expandedKey = sortKey.split('.');
+      expandedKey.forEach((k) => {
+        nestedA = nestedA[k];
+        nestedB = nestedB[k];
+      });
+
+      return nestedA > nestedB ? 1 : -1;
+    }
+
+    if (sortKey === 'lastUpdatedTime') {
+      return val1 - val2;
+    }
+
+    assertNotReached('Unsupported sort key: ' + sortKey);
+    return 0;
   }
-
-  // Compare mojo_base.mojom.TimeDelta microseconds value.
-  if (sortKey == 'cachedAudioVideoWatchtime' ||
-      sortKey == 'actualAudioVideoWatchtime' || sortKey == 'watchtime' ||
-      sortKey == 'duration' || sortKey == 'position') {
-    return val1.microseconds - val2.microseconds;
-  }
-
-  if (sortKey.startsWith('metadata.')) {
-    // Keys with a period denote nested objects.
-    let nestedA = a;
-    let nestedB = b;
-    const expandedKey = sortKey.split('.');
-    expandedKey.forEach((k) => {
-      nestedA = nestedA[k];
-      nestedB = nestedB[k];
-    });
-
-    return nestedA > nestedB;
-  }
-
-  if (sortKey == 'lastUpdatedTime') {
-    return val1 - val2;
-  }
-
-  assertNotReached('Unsupported sort key: ' + sortKey);
-  return 0;
 }
 
 /**
  * Parses utf16 coded string.
- * @param {!mojoBase.mojom.String16} arr
+ * @param {mojoBase.mojom.String16} arr
  * @return {string}
  */
 function decodeString16(arr) {
+  if (!arr) {
+    return '';
+  }
+
   return arr.data.map(ch => String.fromCodePoint(ch)).join('');
 }
 
 /**
- * Formats a field to be displayed in the data table and inserts it into the
- * element.
- * @param {HTMLTableRowElement} td
- * @param {?object} data
- * @param {string} key
- */
-function insertDataField(td, data, key) {
-  if (data === undefined || data === null) {
-    return;
-  }
-
-  if (key == 'origin') {
-    // Format a mojo origin.
-    td.textContent = data.scheme + '://' + data.host;
-    if (data.scheme == 'http' && data.port != '80') {
-      td.textContent += ':' + data.port;
-    } else if (data.scheme == 'https' && data.port != '443') {
-      td.textContent += ':' + data.port;
-    }
-  } else if (key == 'lastUpdatedTime') {
-    // Format a JS timestamp.
-    td.textContent = data ? new Date(data).toISOString() : '';
-  } else if (
-      key == 'cachedAudioVideoWatchtime' ||
-      key == 'actualAudioVideoWatchtime' || key == 'watchtime' ||
-      key == 'duration' || key == 'position') {
-    // Format a mojo timedelta.
-    const secs = (data.microseconds / 1000000);
-    td.textContent = secs.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
-  } else if (key == 'url') {
-    // Format a mojo GURL.
-    td.textContent = data.url;
-  } else if (key == 'hasAudio' || key == 'hasVideo') {
-    // Format a boolean.
-    td.textContent = data ? 'Yes' : 'No';
-  } else if (
-      key == 'title' || key == 'artist' || key == 'album' ||
-      key == 'sourceTitle') {
-    // Format a mojo string16.
-    td.textContent = decodeString16(data);
-  } else if (key == 'artwork') {
-    // Format an array of mojo media images.
-    data.forEach((image) => {
-      const a = document.createElement('a');
-      a.href = image.src.url;
-      a.textContent = image.src.url;
-      a.target = '_blank';
-      td.appendChild(a);
-      td.appendChild(document.createElement('br'));
-    });
-  } else {
-    td.textContent = data;
-  }
-}
-
-class DataTable {
-  /**
-   * @param {!HTMLTableElement} table
-   */
-  constructor(table) {
-    /** @private {!HTMLTableElement} */
-    this.table_ = table;
-
-    /** @private {Object[]} */
-    this.data_ = [];
-
-    // Set table header sort handlers.
-    const headers = this.table_.querySelectorAll('th[sort-key]');
-    headers.forEach((header) => {
-      header.addEventListener('click', this.handleSortClick.bind(this));
-    }, this);
-  }
-
-  handleSortClick(e) {
-    const isCurrentSortColumn = e.target.classList.contains('sort-column');
-
-    // If we are not the sort column then we should become the sort column.
-    if (!isCurrentSortColumn) {
-      const oldSortColumn = document.querySelector('.sort-column');
-      oldSortColumn.classList.remove('sort-column');
-      e.target.classList.add('sort-column');
-    }
-
-    // If we are the current sort column then we should toggle the reverse
-    // attribute to sort in reverse.
-    if (isCurrentSortColumn && e.target.hasAttribute('sort-reverse')) {
-      e.target.removeAttribute('sort-reverse');
-    } else {
-      e.target.setAttribute('sort-reverse', '');
-    }
-
-    this.render();
-  }
-
-  render() {
-    // Find the body of the table and clear it.
-    const body = this.table_.querySelectorAll('tbody')[0];
-    body.innerHTML = '';
-
-    // Get the sort key from the columns to determine which data should be in
-    // which column.
-    const headerCells = Array.from(this.table_.querySelectorAll('thead th'));
-    const dataAndSortKeys = headerCells.map((e) => {
-      return e.getAttribute('sort-key') ? e.getAttribute('sort-key') :
-                                          e.getAttribute('data-key');
-    });
-
-    const currentSortCol = this.table_.querySelectorAll('.sort-column')[0];
-    const currentSortKey = currentSortCol.getAttribute('sort-key');
-    const currentSortReverse = currentSortCol.hasAttribute('sort-reverse');
-
-    // Sort the data based on the current sort key.
-    this.data_.sort((a, b) => {
-      return (currentSortReverse ? -1 : 1) *
-          compareTableItem(currentSortKey, a, b);
-    });
-
-    // Generate the table rows.
-    this.data_.forEach((dataRow) => {
-      const tr = document.createElement('tr');
-      body.appendChild(tr);
-
-      dataAndSortKeys.forEach((key) => {
-        const td = document.createElement('td');
-
-        // Keys with a period denote nested objects.
-        let data = dataRow;
-        const expandedKey = key.split('.');
-        expandedKey.forEach((k) => {
-          data = data[k];
-          key = k;
-        });
-
-        insertDataField(td, data, key);
-        tr.appendChild(td);
-      });
-    });
-  }
-
-  /**
-   * @param {object[]} data The data to update
-   */
-  setData(data) {
-    this.data_ = data;
-    this.render();
-  }
-}
-
-/**
  * Regenerates the stats table.
- * @param {!MediaHistoryStats} stats The stats for the Media History store.
+ * @param {!mediaHistory.mojom.MediaHistoryStats} stats The stats for the Media
+ *     History store.
  */
 function renderStatsTable(stats) {
   statsTableBody.innerHTML = '';
@@ -280,7 +192,7 @@
   }
 
   // Return an empty promise if there is no tab.
-  return new Promise();
+  return new Promise(() => {});
 }
 
 document.addEventListener('DOMContentLoaded', function() {
@@ -288,9 +200,11 @@
 
   statsTableBody = $('stats-table-body');
 
-  originsTable = new DataTable($('origins-table'));
-  playbacksTable = new DataTable($('playbacks-table'));
-  sessionsTable = new DataTable($('sessions-table'));
+  delegate = new MediaHistoryTableDelegate();
+
+  originsTable = new cr.ui.MediaDataTable($('origins-table'), delegate);
+  playbacksTable = new cr.ui.MediaDataTable($('playbacks-table'), delegate);
+  sessionsTable = new cr.ui.MediaDataTable($('sessions-table'), delegate);
 
   cr.ui.decorate('tabbox', cr.ui.TabBox);
 
@@ -305,7 +219,7 @@
     window.location.hash = 'tab-stats';
   } else {
     showTab(window.location.hash.substr(5))
-        .then(pageIsPopulatedResolver.resolve);
+        .then(mediaHistoryPageIsPopulatedResolver.resolve);
   }
 
   // When the tab updates, update the anchor.
diff --git a/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js b/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
index b205ec4..db28100 100644
--- a/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_languages_page/os_add_languages_dialog.js
@@ -166,7 +166,7 @@
     // Close dialog if 'esc' is pressed and the search box is already empty.
     if (e.key == 'Escape' && !this.$.search.getValue().trim()) {
       this.$.dialog.close();
-    } else {
+    } else if (e.key != 'PageDown' && e.key != 'PageUp') {
       this.$.search.scrollIntoViewIfNeeded();
     }
   },
diff --git a/chrome/browser/resources/settings/languages_page/add_languages_dialog.js b/chrome/browser/resources/settings/languages_page/add_languages_dialog.js
index 2a2b607..74e5c3e7 100644
--- a/chrome/browser/resources/settings/languages_page/add_languages_dialog.js
+++ b/chrome/browser/resources/settings/languages_page/add_languages_dialog.js
@@ -166,7 +166,7 @@
     // Close dialog if 'esc' is pressed and the search box is already empty.
     if (e.key == 'Escape' && !this.$.search.getValue().trim()) {
       this.$.dialog.close();
-    } else {
+    } else if (e.key != 'PageDown' && e.key != 'PageUp') {
       this.$.search.scrollIntoViewIfNeeded();
     }
   },
diff --git a/chrome/browser/resources/settings/people_page/BUILD.gn b/chrome/browser/resources/settings/people_page/BUILD.gn
index 3e785a12..526c754 100644
--- a/chrome/browser/resources/settings/people_page/BUILD.gn
+++ b/chrome/browser/resources/settings/people_page/BUILD.gn
@@ -444,7 +444,6 @@
   js_file = "sync_page.js"
   html_file = "sync_page.html"
   html_type = "dom-module"
-  ignore_imports = [ "chrome/browser/resources/settings/privacy_page/personalization_options.html" ]
   namespace_rewrites = settings_namespace_rewrites
   auto_imports = settings_auto_imports + [
                    "ui/webui/resources/html/polymer.html|Polymer,html,flush",
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index dec58eb..17e24ff 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -3,6 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
+import("//ui/webui/resources/tools/js_modulizer.gni")
+import("../settings.gni")
 
 js_type_check("closure_compile") {
   deps = [
@@ -200,23 +203,25 @@
 }
 
 # TODO(crbug.com/1026426): Fix and enable.
-#js_type_check("closure_compile_module") {
-#  is_polymer3 = true
-#  deps = [
-#    ":passwords_leak_detection_toggle.m",
-#    ":personalization_options.m",
-#    ":privacy_page.m",
-#    ":privacy_page_browser_proxy.m",
-#    ":security_keys_bio_enroll_dialog.m",
-#    ":security_keys_browser_proxy.m",
-#    ":security_keys_credential_management_dialog.m",
-#    ":security_keys_pin_field.m",
-#    ":security_keys_reset_dialog.m",
-#    ":security_keys_set_pin_dialog.m",
-#    ":security_keys_subpage.m",
-#    ":security_page.m",
-#  ]
-#}
+js_type_check("closure_compile_module") {
+  is_polymer3 = true
+  deps = [
+    #":passwords_leak_detection_toggle.m",
+    ":personalization_options.m",
+
+    #":privacy_page.m",
+    ":privacy_page_browser_proxy.m",
+
+    #":security_keys_bio_enroll_dialog.m",
+    #":security_keys_browser_proxy.m",
+    #":security_keys_credential_management_dialog.m",
+    #":security_keys_pin_field.m",
+    #":security_keys_reset_dialog.m",
+    #":security_keys_set_pin_dialog.m",
+    #":security_keys_subpage.m",
+    #":security_page.m",
+  ]
+}
 
 js_library("passwords_leak_detection_toggle.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/passwords_leak_detection_toggle.m.js" ]
@@ -229,7 +234,15 @@
 js_library("personalization_options.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/personalization_options.m.js" ]
   deps = [
-    # TODO: Fill those in.
+    ":privacy_page_browser_proxy.m",
+    "..:i18n_setup.m",
+    "..:lifetime_browser_proxy.m",
+    "../controls:settings_toggle_button.m",
+    "../people_page:signout_dialog.m",
+    "../people_page:sync_browser_proxy.m",
+    "../prefs:prefs_behavior.m",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
   extra_deps = [ ":personalization_options_module" ]
 }
@@ -244,9 +257,7 @@
 
 js_library("privacy_page_browser_proxy.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
+  deps = [ "//ui/webui/resources/js:cr.m" ]
   extra_deps = [ ":modulize" ]
 }
 
@@ -314,8 +325,6 @@
   extra_deps = [ ":security_page_module" ]
 }
 
-import("//tools/polymer/polymer.gni")
-
 group("polymer3_elements") {
   public_deps = [
     ":modulize",
@@ -342,6 +351,12 @@
   js_file = "personalization_options.js"
   html_file = "personalization_options.html"
   html_type = "dom-module"
+  namespace_rewrites = settings_namespace_rewrites
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.html|PrivacyPageBrowserProxy,PrivacyPageBrowserProxyImpl,MetricsReporting",
+                   "chrome/browser/resources/settings/people_page/sync_browser_proxy.html|StatusAction,SyncStatus",
+                   "chrome/browser/resources/settings/lifetime_browser_proxy.html|LifetimeBrowserProxyImpl",
+                 ]
 }
 
 polymer_modulizer("privacy_page") {
@@ -392,11 +407,11 @@
   html_type = "dom-module"
 }
 
-import("//ui/webui/resources/tools/js_modulizer.gni")
-
 js_modulizer("modulize") {
   input_files = [
     "privacy_page_browser_proxy.js",
     "security_keys_browser_proxy.js",
   ]
+
+  namespace_rewrites = settings_namespace_rewrites
 }
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.html b/chrome/browser/resources/settings/privacy_page/personalization_options.html
index 872970e6..dace1a9 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.html
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.html
@@ -4,6 +4,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../controls/settings_toggle_button.html">
+<link rel="import" href="../i18n_setup.html">
 <link rel="import" href="../lifetime_browser_proxy.html">
 <link rel="import" href="../people_page/signout_dialog.html">
 <link rel="import" href="../people_page/sync_browser_proxy.html">
diff --git a/chrome/browser/resources/settings/privacy_page/personalization_options.js b/chrome/browser/resources/settings/privacy_page/personalization_options.js
index 58759f35..2076807 100644
--- a/chrome/browser/resources/settings/privacy_page/personalization_options.js
+++ b/chrome/browser/resources/settings/privacy_page/personalization_options.js
@@ -24,8 +24,10 @@
     },
 
     /**
+     * TODO(dpapad): Restore actual type !PrivacyPageVisibility after this file
+     * is no longer reused by chrome://os-settings.
      * Dictionary defining page visibility.
-     * @type {!PrivacyPageVisibility}
+     * @type {!Object}
      */
     pageVisibility: Object,
 
@@ -71,6 +73,9 @@
     },
   },
 
+  /** @private {?settings.PrivacyPageBrowserProxy} */
+  browserProxy_: null,
+
   /**
    * @return {boolean}
    * @private
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js b/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js
index 69fc86b..eb22b0f 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.js
@@ -4,9 +4,13 @@
 
 /** @fileoverview Handles interprocess communication for the privacy page. */
 
+// clang-format off
+// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 cr.define('settings', function() {
   /** @typedef {{enabled: boolean, managed: boolean}} */
-  let MetricsReporting;
+  /* #export */ let MetricsReporting;
 
   /** @typedef {{name: string, value: string, policy: string}} */
   let ResolverOption;
@@ -16,7 +20,7 @@
    * kept in sync with the modes in chrome/browser/net/dns_util.h.
    * @enum {string}
    */
-  const SecureDnsMode = {
+  /* #export */ const SecureDnsMode = {
     OFF: 'off',
     AUTOMATIC: 'automatic',
     SECURE: 'secure',
@@ -27,7 +31,7 @@
    * the management modes in chrome/browser/net/dns_util.h.
    * @enum {number}
    */
-  const SecureDnsUiManagementMode = {
+  /* #export */ const SecureDnsUiManagementMode = {
     NO_OVERRIDE: 0,
     DISABLED_MANAGED: 1,
     DISABLED_PARENTAL_CONTROLS: 2,
@@ -43,7 +47,7 @@
   let SecureDnsSetting;
 
   /** @interface */
-  class PrivacyPageBrowserProxy {
+  /* #export */ class PrivacyPageBrowserProxy {
     // <if expr="_google_chrome and not chromeos">
     /** @return {!Promise<!settings.MetricsReporting>} */
     getMetricsReporting() {}
@@ -93,7 +97,7 @@
   /**
    * @implements {settings.PrivacyPageBrowserProxy}
    */
-  class PrivacyPageBrowserProxyImpl {
+  /* #export */ class PrivacyPageBrowserProxyImpl {
     // <if expr="_google_chrome and not chromeos">
     /** @override */
     getMetricsReporting() {
diff --git a/chrome/browser/resources/settings/settings.gni b/chrome/browser/resources/settings/settings.gni
index 06a34ea..969f0e3 100644
--- a/chrome/browser/resources/settings/settings.gni
+++ b/chrome/browser/resources/settings/settings.gni
@@ -54,15 +54,21 @@
   "Settings.PrefUtil.stringToPrefValue|stringToPrefValue",
   "settings.PrintingBrowserProxy|PrintingBrowserProxy",
   "settings.PrivacyElementInteractions|PrivacyElementInteractions",
+  "settings.PrivacyPageBrowserProxy|PrivacyPageBrowserProxy",
   "settings.ProfileInfoBrowserProxy|ProfileInfoBrowserProxy",
   "settings.ProfileInfo|ProfileInfo",
   "settings.ProfileShortcutStatus|ProfileShortcutStatus",
   "settings.ResetBrowserProxy|ResetBrowserProxy",
+  "settings.ResolverOption|ResolverOption",
   "settings.Route|Route",
   "settings.routes|routes",
   "settings.SearchEnginesBrowserProxy|SearchEnginesBrowserProxy",
   "settings.SearchRequest|SearchRequest",
   "settings.SearchResult|SearchResult",
+  "settings.SecureDnsMode|SecureDnsMode",
+  "settings.SecureDnsSetting|SecureDnsSetting",
+  "settings.SecureDnsUiManagementMode|SecureDnsUiManagementMode",
+  "settings.MetricsReporting|MetricsReporting",
   "settings.SITE_EXCEPTION_WILDCARD|SITE_EXCEPTION_WILDCARD",
   "settings.SiteSettingSource|SiteSettingSource",
   "settings.SiteSettingsPrefsBrowserProxy|SiteSettingsPrefsBrowserProxy",
@@ -75,6 +81,7 @@
   "settings.SyncStatus|SyncStatus",
   "settings.SystemPageBrowserProxy|SystemPageBrowserProxy",
   "settings.RouteObserverBehavior|RouteObserverBehavior",
+  "settings.WebsiteUsageBrowserProxy|WebsiteUsageBrowserProxy",
 
   "action_link.m.js|action_link.js",
 
diff --git a/chrome/browser/resources/settings/settings.js b/chrome/browser/resources/settings/settings.js
index f786fdf..2d53bd4 100644
--- a/chrome/browser/resources/settings/settings.js
+++ b/chrome/browser/resources/settings/settings.js
@@ -56,3 +56,4 @@
 export {routes} from './route.m.js';
 export {Route, Router} from './router.m.js';
 export {SearchEnginesBrowserProxyImpl} from './search_engines_page/search_engines_browser_proxy.m.js';
+export {PrivacyPageBrowserProxyImpl, SecureDnsMode, SecureDnsUiManagementMode} from './privacy_page/privacy_page_browser_proxy.m.js';
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 4d19ead..a63644b 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -1177,11 +1177,11 @@
       <structure name="IDR_SETTINGS_ENSURE_LAZY_LOADED_HTML"
                  file="ensure_lazy_loaded.html"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_WEBSITE_USAGE_PRIVATE_API_HTML"
-                 file="site_settings/website_usage_private_api.html"
+      <structure name="IDR_SETTINGS_WEBSITE_USAGE_BROWSER_PROXY_HTML"
+                 file="site_settings/website_usage_browser_proxy.html"
                  type="chrome_html" />
-      <structure name="IDR_SETTINGS_WEBSITE_USAGE_PRIVATE_API_JS"
-                 file="site_settings/website_usage_private_api.js"
+      <structure name="IDR_SETTINGS_WEBSITE_USAGE_BROWSER_PROXY_JS"
+                 file="site_settings/website_usage_browser_proxy.js"
                  type="chrome_html" />
       <structure name="IDR_SETTINGS_ZOOM_LEVELS_HTML"
                  file="site_settings/zoom_levels.html"
diff --git a/chrome/browser/resources/settings/settings_resources_v3.grdp b/chrome/browser/resources/settings/settings_resources_v3.grdp
index f72286d..a5b0ccb78 100644
--- a/chrome/browser/resources/settings/settings_resources_v3.grdp
+++ b/chrome/browser/resources/settings/settings_resources_v3.grdp
@@ -447,6 +447,16 @@
            use_base_dir="false"
            preprocess="true"
            type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_PERSONALIZATION_OPTIONS_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/personalization_options.m.js"
+           use_base_dir="false"
+           preprocess="true"
+           type="BINDATA" />
+  <include name="IDR_SETTINGS_PRIVACY_PAGE_BROWSER_PROXY_M_JS"
+           file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/privacy_page_browser_proxy.m.js"
+           use_base_dir="false"
+           preprocess="true"
+           type="BINDATA" />
   <include name="IDR_SETTINGS_RESET_PAGE_M_JS"
            file="${root_gen_dir}/chrome/browser/resources/settings/reset_page/reset_page.m.js"
            use_base_dir="false"
diff --git a/chrome/browser/resources/settings/site_settings/BUILD.gn b/chrome/browser/resources/settings/site_settings/BUILD.gn
index d80e6d0..aa73b3c 100644
--- a/chrome/browser/resources/settings/site_settings/BUILD.gn
+++ b/chrome/browser/resources/settings/site_settings/BUILD.gn
@@ -30,7 +30,7 @@
     ":site_list_entry",
     ":site_settings_behavior",
     ":site_settings_prefs_browser_proxy",
-    ":website_usage_private_api",
+    ":website_usage_browser_proxy",
     ":zoom_levels",
   ]
 }
@@ -190,7 +190,7 @@
     ":constants",
     ":site_details_permission",
     ":site_settings_behavior",
-    ":website_usage_private_api",
+    ":website_usage_browser_proxy",
     "..:router",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
@@ -281,7 +281,7 @@
   ]
 }
 
-js_library("website_usage_private_api") {
+js_library("website_usage_browser_proxy") {
   deps = [ "//ui/webui/resources/js:cr" ]
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
@@ -326,8 +326,8 @@
     ":site_list_entry.m",
     ":site_settings_behavior.m",
     ":site_settings_prefs_browser_proxy.m",
+    ":website_usage_browser_proxy.m",
 
-    #    ":website_usage_private_api.m",
     #    ":zoom_levels.m",
   ]
 }
@@ -602,12 +602,10 @@
   extra_deps = [ ":modulize" ]
 }
 
-js_library("website_usage_private_api.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/website_usage_private_api.m.js" ]
-  deps = [
-    # TODO: Fill those in.
-  ]
-  extra_deps = [ ":website_usage_private_api_module" ]
+js_library("website_usage_browser_proxy.m") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.m.js" ]
+  deps = [ "//ui/webui/resources/js:cr.m" ]
+  extra_deps = [ ":modulize" ]
 }
 
 js_library("zoom_levels.m") {
@@ -641,7 +639,6 @@
     ":site_entry_module",
     ":site_list_entry_module",
     ":site_list_module",
-    ":website_usage_private_api_module",
     ":zoom_levels_module",
   ]
 }
@@ -829,12 +826,6 @@
   namespace_rewrites = settings_namespace_rewrites
 }
 
-polymer_modulizer("website_usage_private_api") {
-  js_file = "website_usage_private_api.js"
-  html_file = "website_usage_private_api.html"
-  html_type = "dom-module"
-}
-
 polymer_modulizer("zoom_levels") {
   js_file = "zoom_levels.js"
   html_file = "zoom_levels.html"
@@ -849,6 +840,7 @@
     "local_data_browser_proxy.js",
     "site_settings_behavior.js",
     "site_settings_prefs_browser_proxy.js",
+    "website_usage_browser_proxy.js",
   ]
   namespace_rewrites = settings_namespace_rewrites
 }
diff --git a/chrome/browser/resources/settings/site_settings/site_details.html b/chrome/browser/resources/settings/site_settings/site_details.html
index b78613b..5cf202c 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.html
+++ b/chrome/browser/resources/settings/site_settings/site_details.html
@@ -20,7 +20,7 @@
 <link rel="import" href="constants.html">
 <link rel="import" href="site_details_permission.html">
 <link rel="import" href="site_settings_behavior.html">
-<link rel="import" href="website_usage_private_api.html">
+<link rel="import" href="website_usage_browser_proxy.html">
 
 <dom-module id="site-details">
   <template>
@@ -281,11 +281,6 @@
         </site-details-permission>
       </template>
     </div>
-
-    <website-usage-private-api id="usageApi"
-        website-data-usage="{{storedData_}}"
-        website-cookie-usage="{{numCookies_}}">
-    </website-usage-private-api>
   </template>
   <script src="site_details.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/settings/site_settings/site_details.js b/chrome/browser/resources/settings/site_settings/site_details.js
index 3c639e7..f24d429 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.js
+++ b/chrome/browser/resources/settings/site_settings/site_details.js
@@ -99,12 +99,20 @@
     value: () => loadTimeData.getBoolean('enableWebXrContentSetting'),
   },
 
-  listeners: {
-    'usage-deleted': 'onUsageDeleted_',
-  },
+  /** @private {string} */
+  fetchingForHost_: '',
+
+  /** @private {?settings.WebsiteUsageBrowserProxy} */
+  websiteUsageProxy_: null,
 
   /** @override */
   attached() {
+    this.websiteUsageProxy_ =
+        settings.WebsiteUsageBrowserProxyImpl.getInstance();
+    this.addWebUIListener('usage-total-changed', (host, data, cookies) => {
+      this.onUsageTotalChanged_(host, data, cookies);
+    });
+
     this.addWebUIListener(
         'contentSettingSitePermissionChanged',
         this.onPermissionChanged_.bind(this));
@@ -141,7 +149,9 @@
       if (!valid) {
         settings.Router.getInstance().navigateToPreviousRoute();
       } else {
-        this.$.usageApi.fetchUsageTotal(this.toUrl(this.origin_).hostname);
+        this.fetchingForHost_ = this.toUrl(this.origin_).hostname;
+        this.storedData_ = '';
+        this.websiteUsageProxy_.fetchUsageTotal(this.fetchingForHost_);
         this.updatePermissions_(this.getCategoryList());
       }
     });
@@ -170,6 +180,22 @@
     this.updatePermissions_([category]);
   },
 
+  /**
+   * Callback for when the usage total is known.
+   * @param {string} host The host that the usage was fetched for.
+   * @param {string} usage The string showing how much data the given host
+   *     is using.
+   * @param {string} cookies The string showing how many cookies the given host
+   *     is using.
+   * @private
+   */
+  onUsageTotalChanged_(host, usage, cookies) {
+    if (this.fetchingForHost_ === host) {
+      this.storedData_ = usage;
+      this.numCookies_ = cookies;
+    }
+  },
+
   // <if expr="chromeos">
   prefEnableDrmChanged_() {
     this.updatePermissions_([settings.ContentSettingsTypes.PROTECTED_CONTENT]);
@@ -267,23 +293,12 @@
    */
   onClearStorage_(e) {
     if (this.hasUsage_(this.storedData_, this.numCookies_)) {
-      this.$.usageApi.clearUsage(this.toUrl(this.origin_).href);
-    }
-
-    this.onCloseDialog_(e);
-  },
-
-  /**
-   * Called when usage has been deleted for an origin via a non-Site Details
-   * source, e.g. clear browsing data.
-   * @param {!CustomEvent<!{origin: string}>} event
-   * @private
-   */
-  onUsageDeleted_(event) {
-    if (event.detail.origin == this.toUrl(this.origin_).href) {
+      this.websiteUsageProxy_.clearUsage(this.toUrl(this.origin_).href);
       this.storedData_ = '';
       this.numCookies_ = '';
     }
+
+    this.onCloseDialog_(e);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.html b/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.html
new file mode 100644
index 0000000..82e7669
--- /dev/null
+++ b/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.html
@@ -0,0 +1,2 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<script src="website_usage_browser_proxy.js"></script>
diff --git a/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.js b/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.js
new file mode 100644
index 0000000..e177538
--- /dev/null
+++ b/chrome/browser/resources/settings/site_settings/website_usage_browser_proxy.js
@@ -0,0 +1,39 @@
+// Copyright 2020 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.
+
+// clang-format off
+// #import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
+cr.define('settings', function() {
+  /** @interface */
+  /* #export */ class WebsiteUsageBrowserProxy {
+    /** @param {string} host */
+    fetchUsageTotal(host) {}
+
+    /** @param {string} origin */
+    clearUsage(origin) {}
+  }
+
+  /** @implements {settings.WebsiteUsageBrowserProxy} */
+  /* #export */ class WebsiteUsageBrowserProxyImpl {
+    /** @override */
+    fetchUsageTotal(host) {
+      chrome.send('fetchUsageTotal', [host]);
+    }
+
+    /** @override */
+    clearUsage(origin) {
+      chrome.send('clearUsage', [origin]);
+    }
+  }
+
+  cr.addSingletonGetter(WebsiteUsageBrowserProxyImpl);
+
+  // #cr_define_end
+  return {
+    WebsiteUsageBrowserProxy: WebsiteUsageBrowserProxy,
+    WebsiteUsageBrowserProxyImpl: WebsiteUsageBrowserProxyImpl,
+  };
+});
diff --git a/chrome/browser/resources/settings/site_settings/website_usage_private_api.html b/chrome/browser/resources/settings/site_settings/website_usage_private_api.html
deleted file mode 100644
index d286736..0000000
--- a/chrome/browser/resources/settings/site_settings/website_usage_private_api.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="website_usage_private_api.js"></script>
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
deleted file mode 100644
index 7b959b1b..0000000
--- a/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 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.
-
-cr.define('settings.WebsiteUsagePrivateApi', function() {
-  Polymer({
-    is: 'website-usage-private-api',
-
-    properties: {
-      /**
-       * The amount of data used by the given website.
-       */
-      websiteDataUsage: {
-        type: String,
-        notify: true,
-      },
-
-      /**
-       * The number of cookies used by the given website.
-       */
-      websiteCookieUsage: {
-        type: String,
-        notify: true,
-      },
-    },
-
-    /** @override */
-    attached() {
-      settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance = this;
-    },
-
-    /** @param {string} host */
-    fetchUsageTotal(host) {
-      settings.WebsiteUsagePrivateApi.fetchUsageTotal(host);
-    },
-
-    /**
-     * @param {string} origin
-     */
-    clearUsage(origin) {
-      settings.WebsiteUsagePrivateApi.clearUsage(origin);
-    },
-
-    /** @param {string} origin */
-    notifyUsageDeleted(origin) {
-      this.fire('usage-deleted', {origin: origin});
-    },
-  });
-
-  /**
-   * @type {Object} An instance of the polymer object defined above.
-   * All data will be set here.
-   */
-  const websiteUsagePolymerInstance = null;
-
-  /**
-   * @type {string} The host for which the usage total is being fetched.
-   */
-  let hostName;
-
-  /**
-   * Encapsulates the calls between JS and C++ to fetch how much storage the
-   * host is using.
-   * Will update the data in |websiteUsagePolymerInstance|.
-   */
-  const fetchUsageTotal = function(host) {
-    const instance =
-        settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
-    if (instance != null) {
-      instance.websiteDataUsage = '';
-    }
-
-    hostName = host;
-    chrome.send('fetchUsageTotal', [host]);
-  };
-
-  /**
-   * Callback for when the usage total is known.
-   * @param {string} host The host that the usage was fetched for.
-   * @param {string} usage The string showing how much data the given host
-   *     is using.
-   */
-  const returnUsageTotal = function(host, usage, cookies) {
-    const instance =
-        settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
-    if (instance == null) {
-      return;
-    }
-
-    if (hostName == host) {
-      instance.websiteDataUsage = usage;
-      instance.websiteCookieUsage = cookies;
-    }
-  };
-
-  /**
-   * Deletes the storage being used for a given origin.
-   * @param {string} origin The origin to delete storage for.
-   */
-  const clearUsage = function(origin) {
-    chrome.send('clearUsage', [origin]);
-    const instance =
-        settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
-    if (instance == null) {
-      return;
-    }
-
-    instance.notifyUsageDeleted(origin);
-  };
-
-  // #cr_define_end
-  return {
-    websiteUsagePolymerInstance: websiteUsagePolymerInstance,
-    fetchUsageTotal: fetchUsageTotal,
-    returnUsageTotal: returnUsageTotal,
-    clearUsage: clearUsage,
-  };
-});
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ShareImageFileUtils.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ShareImageFileUtils.java
index 1d23b90fe..b5a00e4 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ShareImageFileUtils.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ShareImageFileUtils.java
@@ -4,16 +4,21 @@
 
 package org.chromium.chrome.browser.share;
 
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Environment;
+import android.provider.MediaStore;
 import android.text.TextUtils;
 
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ApplicationState;
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
@@ -21,14 +26,18 @@
 import org.chromium.base.Log;
 import org.chromium.base.StreamUtil;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.chrome.browser.download.DownloadManagerBridge;
 import org.chromium.content_public.browser.RenderWidgetHostView;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.Clipboard;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Locale;
 
 /**
@@ -46,6 +55,7 @@
     private static final String SHARE_IMAGES_DIRECTORY_NAME = "screenshot";
     private static final String JPEG_EXTENSION = ".jpg";
     private static final String FILE_NUMBER_FORMAT = " (%d)";
+    private static final String MIME_TYPE = "image/JPEG";
 
     /**
      * Check if the file related to |fileUri| is in the |folder|.
@@ -122,25 +132,20 @@
         }
         OnImageSaveListener listener = new OnImageSaveListener() {
             @Override
-            public void onImageSaved(File imageFile) {
-                new AsyncTask<Uri>() {
-                    @Override
-                    protected Uri doInBackground() {
-                        return ContentUriUtils.getContentUriFromFile(imageFile);
-                    }
-
-                    @Override
-                    protected void onPostExecute(Uri uri) {
-                        callback.onResult(uri);
-                    }
-                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            public void onImageSaved(Uri uri, String displayName) {
+                callback.onResult(uri);
             }
             @Override
             public void onImageSaveError() {}
         };
 
         String fileName = String.valueOf(System.currentTimeMillis());
-        saveImage(fileName, "", listener, (fos) -> { writeImageData(fos, jpegImageData); }, true);
+        // Path is passed as a function because in some cases getting the path should be run on a
+        // background thread.
+        saveImage(fileName,
+                ()
+                        -> { return ""; },
+                listener, (fos) -> { writeImageData(fos, jpegImageData); }, true);
     }
 
     /**
@@ -153,15 +158,21 @@
      */
     public static void saveBitmapToExternalStorage(
             final Context context, String fileName, Bitmap bitmap, OnImageSaveListener listener) {
-        String filePath = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getPath();
-        saveImage(fileName, filePath, listener, (fos) -> { writeBitmap(fos, bitmap); }, false);
+        // Passing the path as a function so that it can be called on a background thread in
+        // |saveImage|.
+        saveImage(fileName,
+                ()
+                        -> {
+                    return context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getPath();
+                },
+                listener, (fos) -> { writeBitmap(fos, bitmap); }, false);
     }
 
     /**
-     * Interface for notifying bitmap download result.
+     * Interface for notifying image download result.
      */
     public interface OnImageSaveListener {
-        void onImageSaved(File imageFile);
+        void onImageSaved(Uri uri, String displayName);
         void onImageSaveError();
     }
 
@@ -173,23 +184,30 @@
     }
 
     /**
+     * Interface for providing file path. This is used for passing a function for getting the path
+     * to other function to be called while on a background thread. Should be used on a background
+     * thread.
+     */
+    private interface FilePathProvider { String getPath(); }
+
+    /**
      * Saves image to the given file.
      *
      * @param fileName The File instance of a destination file.
-     * @param filePath The File instance of a destination file.
+     * @param filePathProvider The FilePathProvider for obtaining destination file path.
      * @param listener The OnImageSaveListener to notify the download results.
      * @param writer The FileOutputStreamWriter that writes to given stream.
      * @param isTemporary Indicates whether image should be save to a temporary file.
      */
-    private static void saveImage(String fileName, String filePath, OnImageSaveListener listener,
-            FileOutputStreamWriter writer, boolean isTemporary) {
-        new AsyncTask<File>() {
+    private static void saveImage(String fileName, FilePathProvider filePathProvider,
+            OnImageSaveListener listener, FileOutputStreamWriter writer, boolean isTemporary) {
+        new AsyncTask<Uri>() {
             @Override
-            protected File doInBackground() {
+            protected Uri doInBackground() {
                 FileOutputStream fOut = null;
                 File destFile = null;
                 try {
-                    destFile = createFile(fileName, filePath, isTemporary);
+                    destFile = createFile(fileName, filePathProvider.getPath(), isTemporary);
                     if (destFile != null && destFile.exists()) {
                         fOut = new FileOutputStream(destFile);
                         writer.write(fOut);
@@ -203,7 +221,15 @@
                     StreamUtil.closeQuietly(fOut);
                 }
 
-                return destFile;
+                Uri uri = FileUtils.getUriForFile(destFile);
+                if (!isTemporary) {
+                    if (BuildInfo.isAtLeastQ()) {
+                        uri = addToMediaStore(destFile);
+                    } else {
+                        addCompletedDownload(destFile);
+                    }
+                }
+                return uri;
             }
 
             @Override
@@ -212,8 +238,8 @@
             }
 
             @Override
-            protected void onPostExecute(File imageFile) {
-                if (imageFile == null) {
+            protected void onPostExecute(Uri uri) {
+                if (uri == null) {
                     listener.onImageSaveError();
                     return;
                 }
@@ -223,7 +249,7 @@
                     return;
                 }
 
-                listener.onImageSaved(imageFile);
+                listener.onImageSaved(uri, fileName);
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
@@ -303,6 +329,55 @@
     }
 
     /**
+     * This is a pass through to the {@link AndroidDownloadManager} function of the same name.
+     * @param file The File corresponding to the download.
+     * @return the download ID of this item as assigned by the download manager.
+     */
+    public static long addCompletedDownload(File file) {
+        String title = file.getName();
+        String path = file.getPath();
+        long length = file.length();
+
+        return DownloadManagerBridge.addCompletedDownload(
+                title, title, MIME_TYPE, path, length, null, null, title);
+    }
+
+    @TargetApi(29)
+    public static Uri addToMediaStore(File file) {
+        assert BuildInfo.isAtLeastQ();
+
+        final ContentValues contentValues = new ContentValues();
+        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName());
+        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, MIME_TYPE);
+        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
+
+        ContentResolver database = ContextUtils.getApplicationContext().getContentResolver();
+        Uri insertUri = database.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
+
+        InputStream input = null;
+        OutputStream output = null;
+        try {
+            input = new FileInputStream(file);
+            if (insertUri != null) {
+                output = database.openOutputStream(insertUri);
+            }
+            if (output != null) {
+                byte[] buffer = new byte[4096];
+                int byteCount = 0;
+                while ((byteCount = input.read(buffer)) != -1) {
+                    output.write(buffer, 0, byteCount);
+                }
+            }
+            file.delete();
+        } catch (IOException e) {
+        } finally {
+            StreamUtil.closeQuietly(input);
+            StreamUtil.closeQuietly(output);
+        }
+        return insertUri;
+    }
+
+    /**
      * Captures a screenshot for the provided web contents, persists it and notifies the file
      * provider that the file is ready to be accessed by the client.
      *
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
index 39e50c2..baa6c52 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
@@ -6,6 +6,9 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.net.Uri;
 import android.view.View;
 
 import org.chromium.base.metrics.RecordUserAction;
@@ -13,8 +16,6 @@
 import org.chromium.chrome.browser.share.ShareImageFileUtils;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import java.io.File;
-
 /**
  * QrCodeShareMediator is in charge of calculating and setting values for QrCodeShareViewProperties.
  */
@@ -35,6 +36,7 @@
 
         // TODO(gayane): Request generated QR code bitmap with a callback that sets QRCODE_BITMAP
         // property.
+        mPropertyModel.set(QrCodeShareViewProperties.QRCODE_BITMAP, getTestBitmap());
     }
 
     /** Triggers download for the generated QR code bitmap if available. */
@@ -60,7 +62,7 @@
 
     // ShareImageFileUtils.OnImageSaveListener implementation.
     @Override
-    public void onImageSaved(File imageFile) {
+    public void onImageSaved(Uri uri, String displayName) {
         // TODO(gayane): Maybe need to show confirmation message.
         mPropertyModel.set(QrCodeShareViewProperties.DOWNLOAD_SUCCESSFUL, true);
         RecordUserAction.record("SharingQRCode.DownloadQRCode.Succeeded");
@@ -72,4 +74,15 @@
         mPropertyModel.set(QrCodeShareViewProperties.DOWNLOAD_SUCCESSFUL, false);
         RecordUserAction.record("SharingQRCode.DownloadQRCode.Failed");
     }
+
+    private Bitmap getTestBitmap() {
+        int size = 500;
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+        Canvas canvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(android.graphics.Color.GREEN);
+        canvas.drawRect(0F, 0F, (float) size, (float) size, paint);
+        return bitmap;
+    }
 }
\ No newline at end of file
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
index e5088ea..fcf0146 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/ShareImageFileUtilsTest.java
@@ -4,13 +4,19 @@
 
 package org.chromium.chrome.browser.share;
 
+import android.annotation.TargetApi;
+import android.app.DownloadManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
+import android.provider.MediaStore;
 import android.support.test.filters.SmallTest;
 
 import androidx.core.content.FileProvider;
@@ -22,6 +28,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
@@ -50,8 +57,10 @@
     public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
             new ChromeActivityTestRule<>(ChromeActivity.class);
 
-    private static final long WAIT_TIMEOUT_SECONDS = 5L;
+    private static final long WAIT_TIMEOUT_SECONDS = 30L;
     private static final byte[] TEST_IMAGE_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    private static final String TEST_IMAGE_FILE_NAME = "chrome-test-bitmap";
+    private static final String TEST_IMAGE_FILE_EXTENSION = ".jpg";
 
     private class FileProviderHelper implements ContentUriUtils.FileProviderUtil {
         private static final String API_AUTHORITY_SUFFIX = ".FileProvider";
@@ -89,13 +98,13 @@
     public void setUp() {
         mActivityTestRule.startMainActivityFromLauncher();
         ContentUriUtils.setFileProviderUtil(new FileProviderHelper());
-        clearExternalStorageDir();
     }
 
     @After
     public void tearDown() throws TimeoutException {
         Clipboard.getInstance().setText("");
         clearSharedImages();
+        deleteAllTestImages();
     }
 
     private int fileCount(File file) {
@@ -138,20 +147,47 @@
         // ShareImageFileUtils::clearSharedImages uses AsyncTask.SERIAL_EXECUTOR to schedule a
         // clearing the shared folder job, so schedule a new job and wait for the new job finished
         // to make sure ShareImageFileUtils::clearSharedImages's clearing folder job finished.
+        waitForAsync();
+    }
+
+    private void waitForAsync() throws TimeoutException {
         AsyncTaskRunnableHelper runnableHelper = new AsyncTaskRunnableHelper();
         AsyncTask.SERIAL_EXECUTOR.execute(runnableHelper);
         runnableHelper.waitForCallback(0, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        AsyncTask.THREAD_POOL_EXECUTOR.execute(runnableHelper);
+        runnableHelper.waitForCallback(0, 1, WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
-    public void clearExternalStorageDir() {
+    private void deleteAllTestImages() throws TimeoutException {
         AsyncTask.SERIAL_EXECUTOR.execute(() -> {
-            File externalStorageDir = mActivityTestRule.getActivity().getExternalFilesDir(
-                    Environment.DIRECTORY_DOWNLOADS);
-            String[] children = externalStorageDir.list();
-            for (int i = 0; i < children.length; i++) {
-                new File(externalStorageDir, children[i]).delete();
+            if (BuildInfo.isAtLeastQ()) {
+                deleteMediaStoreFiles();
             }
+            deleteExternalStorageFiles();
         });
+        waitForAsync();
+    }
+
+    @TargetApi(29)
+    private void deleteMediaStoreFiles() {
+        ContentResolver contentResolver = ContextUtils.getApplicationContext().getContentResolver();
+        Cursor cursor =
+                contentResolver.query(MediaStore.Downloads.EXTERNAL_CONTENT_URI, null, null, null);
+        while (cursor.moveToNext()) {
+            long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Downloads._ID));
+            Uri uri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id);
+            contentResolver.delete(uri, null, null);
+        }
+    }
+
+    public void deleteExternalStorageFiles() {
+        File externalStorageDir = ContextUtils.getApplicationContext().getExternalFilesDir(
+                Environment.DIRECTORY_DOWNLOADS);
+        String[] children = externalStorageDir.list();
+        for (int i = 0; i < children.length; i++) {
+            new File(externalStorageDir, children[i]).delete();
+        }
     }
 
     private int fileCountInShareDirectory() throws IOException {
@@ -202,17 +238,25 @@
     @Test
     @SmallTest
     @DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/1056059")
-    public void testSaveBitmap() throws IOException {
-        String fileName = "chrome-test-bitmap";
+    public void testSaveBitmap() throws IOException, TimeoutException {
+        String fileName = TEST_IMAGE_FILE_NAME + "_save_bitmap";
         ShareImageFileUtils.OnImageSaveListener listener =
                 new ShareImageFileUtils.OnImageSaveListener() {
                     @Override
-                    public void onImageSaved(File imageFile) {
+                    public void onImageSaved(Uri uri, String displayName) {
+                        Assert.assertNotNull(uri);
+                        Assert.assertEquals(fileName, displayName);
                         AsyncTask.SERIAL_EXECUTOR.execute(() -> {
-                            Assert.assertTrue(imageFile.exists());
-                            Assert.assertTrue(imageFile.isFile());
-                            Assert.assertTrue(imageFile.getPath().contains(fileName));
+                            File file = new File(uri.getPath());
+                            Assert.assertTrue(file.exists());
+                            Assert.assertTrue(file.isFile());
                         });
+
+                        // Wait for the above checks to complete.
+                        try {
+                            waitForAsync();
+                        } catch (TimeoutException ex) {
+                        }
                     }
 
                     @Override
@@ -222,23 +266,88 @@
                 };
         ShareImageFileUtils.saveBitmapToExternalStorage(
                 mActivityTestRule.getActivity(), fileName, getTestBitmap(), listener);
+        waitForAsync();
+    }
+
+    @Test
+    @SmallTest
+    @DisableIf.Build(sdk_is_less_than = 29)
+    public void testSaveBitmapAndMediaStore() throws IOException, TimeoutException {
+        String fileName = TEST_IMAGE_FILE_NAME + "_mediastore";
+        ShareImageFileUtils.OnImageSaveListener listener =
+                new ShareImageFileUtils.OnImageSaveListener() {
+                    @Override
+                    public void onImageSaved(Uri uri, String displayName) {
+                        Assert.assertNotNull(uri);
+                        Assert.assertEquals(fileName, displayName);
+                        AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+                            Cursor cursor =
+                                    mActivityTestRule.getActivity().getContentResolver().query(
+                                            uri, null, null, null, null);
+                            Assert.assertNotNull(cursor);
+                            Assert.assertTrue(cursor.moveToFirst());
+                            Assert.assertEquals(fileName + TEST_IMAGE_FILE_EXTENSION,
+                                    cursor.getString(cursor.getColumnIndex(
+                                            MediaStore.MediaColumns.DISPLAY_NAME)));
+                        });
+
+                        // Wait for the above checks to complete.
+                        try {
+                            waitForAsync();
+                        } catch (TimeoutException ex) {
+                        }
+                    }
+
+                    @Override
+                    public void onImageSaveError() {
+                        Assert.fail();
+                    }
+                };
+        ShareImageFileUtils.saveBitmapToExternalStorage(
+                mActivityTestRule.getActivity(), fileName, getTestBitmap(), listener);
+        waitForAsync();
     }
 
     @Test
     @SmallTest
     public void testGetNextAvailableFile() throws IOException {
-        String filename = "chrome-test-bitmap";
-        String extension = ".jpg";
-
+        String fileName = TEST_IMAGE_FILE_NAME + "_next_availble";
         File externalStorageDir = mActivityTestRule.getActivity().getExternalFilesDir(
                 Environment.DIRECTORY_DOWNLOADS);
         File imageFile = ShareImageFileUtils.getNextAvailableFile(
-                externalStorageDir.getPath(), filename, extension);
+                externalStorageDir.getPath(), fileName, TEST_IMAGE_FILE_EXTENSION);
         Assert.assertTrue(imageFile.exists());
 
         File imageFile2 = ShareImageFileUtils.getNextAvailableFile(
-                externalStorageDir.getPath(), filename, extension);
+                externalStorageDir.getPath(), fileName, TEST_IMAGE_FILE_EXTENSION);
         Assert.assertTrue(imageFile2.exists());
         Assert.assertNotEquals(imageFile.getPath(), imageFile2.getPath());
     }
+
+    @Test
+    @SmallTest
+    @DisableIf.Build(sdk_is_greater_than = 28)
+    public void testAddCompletedDownload() throws IOException {
+        String filename =
+                TEST_IMAGE_FILE_NAME + "_add_completed_download" + TEST_IMAGE_FILE_EXTENSION;
+        File externalStorageDir = mActivityTestRule.getActivity().getExternalFilesDir(
+                Environment.DIRECTORY_DOWNLOADS);
+        File qrcodeFile = new File(externalStorageDir, filename);
+        Assert.assertTrue(qrcodeFile.createNewFile());
+
+        long downloadId = ShareImageFileUtils.addCompletedDownload(qrcodeFile);
+        Assert.assertNotEquals(0L, downloadId);
+
+        DownloadManager downloadManager =
+                (DownloadManager) mActivityTestRule.getActivity().getSystemService(
+                        Context.DOWNLOAD_SERVICE);
+        DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
+        Cursor c = downloadManager.query(query);
+
+        Assert.assertNotNull(c);
+        Assert.assertTrue(c.moveToFirst());
+        Assert.assertEquals(
+                filename, c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TITLE)));
+        c.close();
+    }
 }
diff --git a/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.cc b/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.cc
index b2cb63a..5aa3448 100644
--- a/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.cc
+++ b/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.cc
@@ -11,11 +11,11 @@
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
+#include "content/public/browser/web_contents.h"
 
 namespace {
 
 void OnParentPermissionDialogComplete(
-    std::unique_ptr<ParentPermissionDialog> dialog,
     extensions::SupervisedUserServiceDelegate::
         ParentPermissionDialogDoneCallback delegate_done_callback,
     ParentPermissionDialog::Result result) {
@@ -73,25 +73,15 @@
         content::BrowserContext* context,
         content::WebContents* contents,
         ParentPermissionDialogDoneCallback done_callback) {
-  std::unique_ptr<ParentPermissionDialog> dialog =
-      std::make_unique<ParentPermissionDialog>(
-          Profile::FromBrowserContext(context));
+  ParentPermissionDialog::DoneCallback inner_done_callback = base::BindOnce(
+      &::OnParentPermissionDialogComplete, std::move(done_callback));
 
-  // Cache the pointer so we can show the dialog after we pass
-  // ownership to the callback.
-  ParentPermissionDialog* dialog_ptr = dialog.get();
-
-  // Ownership of the dialog passes to the callback.  This allows us
-  // to have as many instances of the dialog as calls to the management
-  // API.
-  ParentPermissionDialog::DoneCallback inner_done_callback =
-      base::BindOnce(&::OnParentPermissionDialogComplete, std::move(dialog),
-                     std::move(done_callback));
-
-  // This is safe because moving a unique_ptr doesn't change the underlying
-  // object's address.
-  dialog_ptr->ShowPromptForExtensionInstallation(
-      contents, &extension, SkBitmap(), std::move(inner_done_callback));
+  parent_permission_dialog_ =
+      ParentPermissionDialog::CreateParentPermissionDialogForExtension(
+          Profile::FromBrowserContext(context), contents,
+          contents->GetTopLevelNativeWindow(), gfx::ImageSkia(), &extension,
+          std::move(inner_done_callback));
+  parent_permission_dialog_->ShowDialog();
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.h b/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.h
index de1e585..4ffd8e4 100644
--- a/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.h
+++ b/chrome/browser/supervised_user/supervised_user_service_management_api_delegate.h
@@ -11,6 +11,8 @@
 class BrowserContext;
 }
 
+class ParentPermissionDialog;
+
 namespace extensions {
 
 class SupervisedUserServiceManagementAPIDelegate
@@ -34,6 +36,9 @@
       content::WebContents* contents,
       extensions::SupervisedUserServiceDelegate::
           ParentPermissionDialogDoneCallback done_callback) override;
+
+ private:
+  std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/tab/state/DEPS b/chrome/browser/tab/state/DEPS
new file mode 100644
index 0000000..ff9622f
--- /dev/null
+++ b/chrome/browser/tab/state/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/leveldb_proto",
+]
diff --git a/chrome/browser/tab/state/tab_state_db.cc b/chrome/browser/tab/state/tab_state_db.cc
new file mode 100644
index 0000000..3ffb701
--- /dev/null
+++ b/chrome/browser/tab/state/tab_state_db.cc
@@ -0,0 +1,129 @@
+// Copyright 2020 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/tab/state/tab_state_db.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/leveldb_proto/public/proto_database_provider.h"
+
+namespace {
+
+const char kTabStateDBFolder[] = "tab_state_db";
+leveldb::ReadOptions CreateReadOptions() {
+  leveldb::ReadOptions opts;
+  opts.fill_cache = false;
+  return opts;
+}
+
+bool DatabasePrefixFilter(const std::string& key_prefix,
+                          const std::string& key) {
+  return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
+}
+
+}  // namespace
+
+TabStateDB::TabStateDB(
+    leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
+    const base::FilePath& profile_directory,
+    base::OnceClosure closure)
+    : database_status_(leveldb_proto::Enums::InitStatus::kNotInitialized),
+      storage_database_(
+          proto_database_provider->GetDB<tab_state_db::TabStateContentProto>(
+              leveldb_proto::ProtoDbType::TAB_STATE_DATABASE,
+              profile_directory.AppendASCII(kTabStateDBFolder),
+              base::CreateSequencedTaskRunner(
+                  {base::ThreadPool(), base::MayBlock(),
+                   base::TaskPriority::USER_VISIBLE}))) {
+  storage_database_->Init(base::BindOnce(&TabStateDB::OnDatabaseInitialized,
+                                         weak_ptr_factory_.GetWeakPtr(),
+                                         std::move(closure)));
+}
+
+TabStateDB::~TabStateDB() = default;
+
+bool TabStateDB::IsInitialized() const {
+  return database_status_ == leveldb_proto::Enums::InitStatus::kOK;
+}
+
+void TabStateDB::LoadContent(const std::string& key, LoadCallback callback) {
+  storage_database_->LoadEntriesWithFilter(
+      base::BindRepeating(&DatabasePrefixFilter, key), CreateReadOptions(),
+      /* target_prefix */ "",
+      base::BindOnce(&TabStateDB::OnLoadContent, weak_ptr_factory_.GetWeakPtr(),
+                     std::move(callback)));
+}
+
+void TabStateDB::InsertContent(const std::string& key,
+                               const std::vector<uint8_t>& value,
+                               OperationCallback callback) {
+  auto contents_to_save = std::make_unique<ContentEntry>();
+  tab_state_db::TabStateContentProto proto;
+  proto.set_key(key);
+  proto.set_content_data(value.data(), value.size());
+  contents_to_save->emplace_back(proto.key(), std::move(proto));
+  storage_database_->UpdateEntries(
+      std::move(contents_to_save), std::make_unique<std::vector<std::string>>(),
+      base::BindOnce(&TabStateDB::OnOperationCommitted,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void TabStateDB::DeleteContent(const std::string& key,
+                               OperationCallback callback) {
+  storage_database_->UpdateEntriesWithRemoveFilter(
+      std::make_unique<ContentEntry>(),
+      std::move(base::BindRepeating(&DatabasePrefixFilter, std::move(key))),
+      base::BindOnce(&TabStateDB::OnOperationCommitted,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void TabStateDB::DeleteAllContent(OperationCallback callback) {
+  storage_database_->Destroy(std::move(callback));
+}
+
+TabStateDB::TabStateDB(
+    std::unique_ptr<leveldb_proto::ProtoDatabase<
+        tab_state_db::TabStateContentProto>> storage_database,
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    base::OnceClosure closure)
+    : database_status_(leveldb_proto::Enums::InitStatus::kNotInitialized),
+      storage_database_(std::move(storage_database)) {
+  storage_database_->Init(base::BindOnce(&TabStateDB::OnDatabaseInitialized,
+                                         weak_ptr_factory_.GetWeakPtr(),
+                                         std::move(closure)));
+}
+
+void TabStateDB::OnDatabaseInitialized(
+    base::OnceClosure closure,
+    leveldb_proto::Enums::InitStatus status) {
+  DCHECK_EQ(database_status_,
+            leveldb_proto::Enums::InitStatus::kNotInitialized);
+  database_status_ = status;
+  std::move(closure).Run();
+}
+
+void TabStateDB::OnLoadContent(
+    LoadCallback callback,
+    bool success,
+    std::unique_ptr<std::vector<tab_state_db::TabStateContentProto>> content) {
+  std::vector<KeyAndValue> results;
+  if (success) {
+    for (const auto& proto : *content) {
+      DCHECK(proto.has_key());
+      DCHECK(proto.has_content_data());
+      results.emplace_back(proto.key(),
+                           std::vector<uint8_t>(proto.content_data().begin(),
+                                                proto.content_data().end()));
+    }
+  }
+  std::move(callback).Run(success, std::move(results));
+}
+
+void TabStateDB::OnOperationCommitted(OperationCallback callback,
+                                      bool success) {
+  std::move(callback).Run(success);
+}
diff --git a/chrome/browser/tab/state/tab_state_db.h b/chrome/browser/tab/state/tab_state_db.h
new file mode 100644
index 0000000..c285e92
--- /dev/null
+++ b/chrome/browser/tab/state/tab_state_db.h
@@ -0,0 +1,106 @@
+// Copyright 2020 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_TAB_STATE_TAB_STATE_DB_H_
+#define CHROME_BROWSER_TAB_STATE_TAB_STATE_DB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/browser/tab/state/tab_state_db_content.pb.h"
+#include "components/leveldb_proto/public/proto_database.h"
+
+namespace leveldb_proto {
+class ProtoDatabaseProvider;
+}  // namespace leveldb_proto
+
+class TabStateDBTest;
+
+// TabStateDatabase is leveldb backend store for NonCriticalPersistedTabData.
+// NonCriticalPersistedTabData is an extension of TabState where data for
+// new features which are not critical to the core functionality of the app
+// are acquired and persisted across restarts. The intended key format is
+// <NonCriticalPersistedTabData id>_<Tab id>
+
+// NonCriticalPersistedTabData is stored in key/value pairs.
+// TODO(crbug.com/1061258) add a KeyedService so TabStateDB is per profile
+class TabStateDB {
+ public:
+  using KeyAndValue = std::pair<std::string, std::vector<uint8_t>>;
+
+  // Callback when content is acquired
+  using LoadCallback = base::OnceCallback<void(bool, std::vector<KeyAndValue>)>;
+
+  // Used for confirming an operation was completed successfully (e.g.
+  // insert, delete). This will be invoked on a different SequenceRunner
+  // to TabStateDB.
+  using OperationCallback = base::OnceCallback<void(bool)>;
+
+  // Entry in the database
+  using ContentEntry = leveldb_proto::ProtoDatabase<
+      tab_state_db::TabStateContentProto>::KeyEntryVector;
+
+  // Initializes the database
+  TabStateDB(leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
+             const base::FilePath& profile_directory,
+             base::OnceClosure closure);
+
+  ~TabStateDB();
+
+  // Returns true if initialization has finished successfully, otherwise false.
+  bool IsInitialized() const;
+
+  // Loads the content data for the key and passes them to the callback
+  void LoadContent(const std::string& key, LoadCallback callback);
+
+  // Inserts a value for a given key and passes the result (success/failure) to
+  // OperationCallback
+  void InsertContent(const std::string& key,
+                     const std::vector<uint8_t>& value,
+                     OperationCallback callback);
+
+  // Deletes content in the database, matching all keys which have a prefix
+  // that matches the key
+  void DeleteContent(const std::string& key, OperationCallback callback);
+
+  // Delete all content in the database
+  void DeleteAllContent(OperationCallback callback);
+
+ private:
+  friend class ::TabStateDBTest;
+  // Used for tests
+  explicit TabStateDB(std::unique_ptr<leveldb_proto::ProtoDatabase<
+                          tab_state_db::TabStateContentProto>> storage_database,
+                      scoped_refptr<base::SequencedTaskRunner> task_runner,
+                      base::OnceClosure closure);
+
+  // Passes back database status following database initialization
+  void OnDatabaseInitialized(base::OnceClosure closure,
+                             leveldb_proto::Enums::InitStatus status);
+
+  // Callback when content is loaded
+  void OnLoadContent(
+      LoadCallback callback,
+      bool success,
+      std::unique_ptr<std::vector<tab_state_db::TabStateContentProto>> content);
+
+  // Callback when an operation (e.g. insert or delete) is called
+  void OnOperationCommitted(OperationCallback callback, bool success);
+
+  // Status of the database initialization.
+  leveldb_proto::Enums::InitStatus database_status_;
+
+  // The database for storing content storage information.
+  std::unique_ptr<
+      leveldb_proto::ProtoDatabase<tab_state_db::TabStateContentProto>>
+      storage_database_;
+
+  base::WeakPtrFactory<TabStateDB> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(TabStateDB);
+};
+
+#endif  // CHROME_BROWSER_TAB_STATE_TAB_STATE_DB_H_
diff --git a/chrome/browser/tab/state/tab_state_db_content.proto b/chrome/browser/tab/state/tab_state_db_content.proto
new file mode 100644
index 0000000..4cacfa3
--- /dev/null
+++ b/chrome/browser/tab/state/tab_state_db_content.proto
@@ -0,0 +1,18 @@
+// Copyright 2020 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 tab_state_db;
+
+// Used for storing TabState Content.
+message TabStateContentProto {
+  // Original key for data.
+  optional string key = 1;
+
+  // Content data.
+  optional bytes content_data = 2;
+}
diff --git a/chrome/browser/tab/state/tab_state_db_unittest.cc b/chrome/browser/tab/state/tab_state_db_unittest.cc
new file mode 100644
index 0000000..1f52910e
--- /dev/null
+++ b/chrome/browser/tab/state/tab_state_db_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright 2020 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/tab/state/tab_state_db.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/test/task_environment.h"
+#include "components/leveldb_proto/testing/fake_db.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace {
+const char kMockKey[] = "key";
+const char kMockKeyPrefix[] = "k";
+const std::vector<uint8_t> kMockValue = {0xfa, 0x5b, 0x4c, 0x12};
+}  // namespace
+
+class TabStateDBTest : public testing::Test {
+ public:
+  TabStateDBTest() : content_db_(nullptr) {}
+
+  // Initialize the test database
+  void InitDatabase() {
+    auto storage_db = std::make_unique<
+        leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>>(
+        &content_db_storage_);
+    content_db_ = storage_db.get();
+    base::RunLoop run_loop;
+    tab_state_db_ = base::WrapUnique(new TabStateDB(
+        std::move(storage_db),
+        base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+                                         base::TaskPriority::USER_VISIBLE}),
+        run_loop.QuitClosure()));
+
+    MockInitCallback(content_db_, leveldb_proto::Enums::InitStatus::kOK);
+    run_loop.Run();
+  }
+
+  // Wait for all tasks to be cleared off the queue
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+  void MockInitCallback(leveldb_proto::test::FakeDB<
+                            tab_state_db::TabStateContentProto>* storage_db,
+                        leveldb_proto::Enums::InitStatus status) {
+    storage_db->InitStatusCallback(status);
+    RunUntilIdle();
+  }
+
+  void MockInsertCallback(leveldb_proto::test::FakeDB<
+                              tab_state_db::TabStateContentProto>* storage_db,
+                          bool result) {
+    storage_db->UpdateCallback(result);
+    RunUntilIdle();
+  }
+
+  void MockLoadCallback(leveldb_proto::test::FakeDB<
+                            tab_state_db::TabStateContentProto>* storage_db,
+                        bool res) {
+    storage_db->LoadCallback(res);
+    RunUntilIdle();
+  }
+
+  void MockDeleteCallback(leveldb_proto::test::FakeDB<
+                              tab_state_db::TabStateContentProto>* storage_db,
+                          bool res) {
+    storage_db->UpdateCallback(res);
+    RunUntilIdle();
+  }
+
+  void OperationEvaluation(base::OnceClosure closure,
+                           bool expected_success,
+                           bool actual_success) {
+    EXPECT_EQ(expected_success, actual_success);
+    std::move(closure).Run();
+  }
+
+  void GetEvaluation(base::OnceClosure closure,
+                     std::vector<TabStateDB::KeyAndValue> expected,
+                     bool result,
+                     std::vector<TabStateDB::KeyAndValue> found) {
+    for (size_t i = 0; i < expected.size(); i++) {
+      EXPECT_EQ(found[i].first, expected[i].first);
+      EXPECT_EQ(found[i].second, expected[i].second);
+    }
+    std::move(closure).Run();
+  }
+
+  TabStateDB* tab_state_db() { return tab_state_db_.get(); }
+  leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>*
+  content_db() {
+    return content_db_;
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  std::map<std::string, tab_state_db::TabStateContentProto> content_db_storage_;
+  leveldb_proto::test::FakeDB<tab_state_db::TabStateContentProto>* content_db_;
+  std::unique_ptr<TabStateDB> tab_state_db_;
+
+  DISALLOW_COPY_AND_ASSIGN(TabStateDBTest);
+};
+
+TEST_F(TabStateDBTest, TestInit) {
+  InitDatabase();
+  EXPECT_EQ(true, tab_state_db()->IsInitialized());
+}
+
+TEST_F(TabStateDBTest, TestKeyInsertionSucceeded) {
+  InitDatabase();
+  base::RunLoop run_loop[2];
+  tab_state_db()->InsertContent(
+      kMockKey, kMockValue,
+      base::BindOnce(&TabStateDBTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[0].QuitClosure(), true));
+  MockInsertCallback(content_db(), true);
+  run_loop[0].Run();
+  std::vector<TabStateDB::KeyAndValue> expected;
+  expected.emplace_back(kMockKey, kMockValue);
+  tab_state_db()->LoadContent(
+      kMockKey,
+      base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
+                     run_loop[1].QuitClosure(), expected));
+  MockLoadCallback(content_db(), true);
+  run_loop[1].Run();
+}
+
+TEST_F(TabStateDBTest, TestKeyInsertionFailed) {
+  InitDatabase();
+  base::RunLoop run_loop[2];
+  tab_state_db()->InsertContent(
+      kMockKey, kMockValue,
+      base::BindOnce(&TabStateDBTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[0].QuitClosure(), false));
+  MockInsertCallback(content_db(), false);
+  run_loop[0].Run();
+  std::vector<TabStateDB::KeyAndValue> expected;
+  tab_state_db()->LoadContent(
+      kMockKey,
+      base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
+                     run_loop[1].QuitClosure(), expected));
+  MockLoadCallback(content_db(), true);
+  run_loop[1].Run();
+}
+
+TEST_F(TabStateDBTest, TestKeyInsertionPrefix) {
+  InitDatabase();
+  base::RunLoop run_loop[2];
+  tab_state_db()->InsertContent(
+      kMockKey, kMockValue,
+      base::BindOnce(&TabStateDBTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[0].QuitClosure(), true));
+  MockInsertCallback(content_db(), true);
+  run_loop[0].Run();
+  std::vector<TabStateDB::KeyAndValue> expected;
+  expected.emplace_back(kMockKey, kMockValue);
+  tab_state_db()->LoadContent(
+      kMockKeyPrefix,
+      base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
+                     run_loop[1].QuitClosure(), expected));
+  MockLoadCallback(content_db(), true);
+  run_loop[1].Run();
+}
+
+TEST_F(TabStateDBTest, TestDelete) {
+  InitDatabase();
+  base::RunLoop run_loop[4];
+  tab_state_db()->InsertContent(
+      kMockKey, kMockValue,
+      base::BindOnce(&TabStateDBTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[0].QuitClosure(), true));
+  MockInsertCallback(content_db(), true);
+  run_loop[0].Run();
+  std::vector<TabStateDB::KeyAndValue> expected;
+  expected.emplace_back(kMockKey, kMockValue);
+  tab_state_db()->LoadContent(
+      kMockKey,
+      base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
+                     run_loop[1].QuitClosure(), expected));
+  MockLoadCallback(content_db(), true);
+  run_loop[1].Run();
+
+  tab_state_db()->DeleteContent(
+      kMockKey,
+      base::BindOnce(&TabStateDBTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[2].QuitClosure(), true));
+  MockDeleteCallback(content_db(), true);
+  run_loop[2].Run();
+
+  std::vector<TabStateDB::KeyAndValue> expected_after_delete;
+  tab_state_db()->LoadContent(
+      kMockKey,
+      base::BindOnce(&TabStateDBTest::GetEvaluation, base::Unretained(this),
+                     run_loop[3].QuitClosure(), expected_after_delete));
+  MockLoadCallback(content_db(), true);
+  run_loop[3].Run();
+}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 966d13a..f347a4b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3957,7 +3957,6 @@
       "ash/launcher/shelf_spinner_controller.h",
       "ash/launcher/shelf_spinner_item_controller.cc",
       "ash/launcher/shelf_spinner_item_controller.h",
-      "supervised_user/parent_permission_dialog.cc",
       "supervised_user/parent_permission_dialog.h",
       "views/apps/app_dialog/app_block_dialog_view.cc",
       "views/apps/app_dialog/app_block_dialog_view.h",
diff --git a/chrome/browser/ui/gtk/gtk_util.cc b/chrome/browser/ui/gtk/gtk_util.cc
index 054dc2d..a6921c6c 100644
--- a/chrome/browser/ui/gtk/gtk_util.cc
+++ b/chrome/browser/ui/gtk/gtk_util.cc
@@ -281,6 +281,11 @@
   }
 }
 
+SkColor GdkRgbaToSkColor(const GdkRGBA& color) {
+  return SkColorSetARGB(color.alpha * 255, color.red * 255, color.green * 255,
+                        color.blue * 255);
+}
+
 NO_SANITIZE("cfi-icall")
 ScopedStyleContext AppendCssNodeToStyleContext(GtkStyleContext* context,
                                                const std::string& css_node) {
@@ -423,11 +428,6 @@
   return context;
 }
 
-SkColor GdkRgbaToSkColor(const GdkRGBA& color) {
-  return SkColorSetARGB(color.alpha * 255, color.red * 255, color.green * 255,
-                        color.blue * 255);
-}
-
 SkColor GetFgColorFromStyleContext(GtkStyleContext* context) {
   GdkRGBA color;
 #if GTK_CHECK_VERSION(3, 90, 0)
diff --git a/chrome/browser/ui/gtk/gtk_util.h b/chrome/browser/ui/gtk/gtk_util.h
index 4ba7b180..0ff280f 100644
--- a/chrome/browser/ui/gtk/gtk_util.h
+++ b/chrome/browser/ui/gtk/gtk_util.h
@@ -121,6 +121,8 @@
 // Converts ui::NativeTheme::State to GtkStateFlags.
 GtkStateFlags StateToStateFlags(ui::NativeTheme::State state);
 
+SkColor GdkRgbaToSkColor(const GdkRGBA& color);
+
 // If |context| is nullptr, creates a new top-level style context
 // specified by parsing |css_node|.  Otherwise, creates the child
 // context with |context| as the parent.
diff --git a/chrome/browser/ui/gtk/native_theme_gtk.cc b/chrome/browser/ui/gtk/native_theme_gtk.cc
index 139a8f92..8f45bf92 100644
--- a/chrome/browser/ui/gtk/native_theme_gtk.cc
+++ b/chrome/browser/ui/gtk/native_theme_gtk.cc
@@ -279,6 +279,14 @@
       return GetBgColor(GtkCheckVersion(3, 20) ? "GtkTextView#textview.view"
                                                : "GtkTextView.view");
     case ui::NativeTheme::kColorId_TextfieldPlaceholderColor:
+      if (!GtkCheckVersion(3, 90)) {
+        auto context = GetStyleContextFromCss("GtkEntry#entry");
+        // This is copied from gtkentry.c.
+        GdkRGBA fg = {0.5, 0.5, 0.5};
+        gtk_style_context_lookup_color(context, "placeholder_text_color", &fg);
+        return GdkRgbaToSkColor(fg);
+      }
+      return GetFgColor("GtkEntry#entry #text #placeholder");
     case ui::NativeTheme::kColorId_TextfieldReadOnlyColor:
       return GetFgColor(GtkCheckVersion(3, 20)
                             ? "GtkTextView#textview.view:disabled #text"
diff --git a/chrome/browser/ui/supervised_user/parent_permission_dialog.cc b/chrome/browser/ui/supervised_user/parent_permission_dialog.cc
deleted file mode 100644
index 0f5b00f..0000000
--- a/chrome/browser/ui/supervised_user/parent_permission_dialog.cc
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/extensions/extension_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/supervised_user/supervised_user_service.h"
-#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
-#include "components/signin/public/identity_manager/access_token_fetcher.h"
-#include "components/signin/public/identity_manager/access_token_info.h"
-#include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/signin/public/identity_manager/scope_set.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/browser/image_loader.h"
-#include "extensions/common/extension_icon_set.h"
-#include "extensions/common/extension_resource.h"
-#include "extensions/common/manifest_handlers/icons_handler.h"
-#include "google_apis/gaia/gaia_auth_consumer.h"
-#include "google_apis/gaia/gaia_auth_fetcher.h"
-#include "google_apis/gaia/gaia_constants.h"
-#include "ui/base/resource/scale_factor.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/image/image_skia.h"
-
-namespace {
-// Returns bitmap for the default icon with size equal to the default icon's
-// pixel size under maximal supported scale factor.
-const gfx::ImageSkia& GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
-  return is_app ? extensions::util::GetDefaultAppIcon()
-                : extensions::util::GetDefaultExtensionIcon();
-}
-
-signin::IdentityManager* test_identity_manager = nullptr;
-}  // namespace
-
-ParentPermissionDialog::ParentPermissionDialog(Profile* profile)
-    : profile_(profile) {
-  DCHECK(profile_);
-  DCHECK(profile_->IsChild());
-}
-
-ParentPermissionDialog::~ParentPermissionDialog() {
-  // Close the underlying widget if this object is deleted.
-  if (close_dialog_view_callback_)
-    std::move(close_dialog_view_callback_).Run();
-}
-
-void ParentPermissionDialog::ShowPrompt(content::WebContents* web_contents,
-                                        const base::string16& message,
-                                        const SkBitmap& icon,
-                                        DoneCallback callback) {
-  callback_ = std::move(callback);
-  DCHECK(callback_);
-  DCHECK(!web_contents_);
-  web_contents_ = web_contents;
-  message_ = message;
-
-  if (!icon.isNull()) {
-    const gfx::Image image = gfx::Image::CreateFrom1xBitmap(icon);
-    if (!image.IsEmpty())
-      icon_ = *image.ToImageSkia();
-  }
-  LoadParentEmailAddresses();
-  ShowPromptInternal(false /* show_password_incorrect */);
-}
-
-void ParentPermissionDialog::ShowPromptForExtensionInstallation(
-    content::WebContents* web_contents,
-    const extensions::Extension* extension,
-    const SkBitmap& fallback_icon,
-    DoneCallback callback) {
-  callback_ = std::move(callback);
-  DCHECK(callback_);
-  DCHECK(!web_contents_);
-  web_contents_ = web_contents;
-  extension_ = extension;
-
-  if (!fallback_icon.isNull()) {
-    const gfx::Image image = gfx::Image::CreateFrom1xBitmap(fallback_icon);
-    if (!image.IsEmpty())
-      icon_ = *image.ToImageSkia();
-  }
-
-  LoadParentEmailAddresses();
-  // Prompt is shown after extension icon is loaded.
-  LoadExtensionIcon();
-}
-
-bool ParentPermissionDialog::CredentialWasInvalid() const {
-  return invalid_credential_received_;
-}
-
-// static
-void ParentPermissionDialog::SetFakeIdentityManagerForTesting(
-    signin::IdentityManager* identity_manager) {
-  test_identity_manager = identity_manager;
-}
-
-void ParentPermissionDialog::LoadParentEmailAddresses() {
-  // Get the parents' email addresses.  There can be a max of 2 parent email
-  // addresses, the primary and the secondary.
-  SupervisedUserService* service =
-      SupervisedUserServiceFactory::GetForProfile(profile_);
-
-  base::string16 primary_parent_email =
-      base::UTF8ToUTF16(service->GetCustodianEmailAddress());
-  if (!primary_parent_email.empty())
-    parent_permission_email_addresses_.push_back(primary_parent_email);
-
-  base::string16 secondary_parent_email =
-      base::UTF8ToUTF16(service->GetSecondCustodianEmailAddress());
-  if (!secondary_parent_email.empty())
-    parent_permission_email_addresses_.push_back(secondary_parent_email);
-
-  if (parent_permission_email_addresses_.empty()) {
-    // TODO(danan):  Add UMA stat for this failure.
-    // https://crbug.com/1049418
-    SendResult(Result::kParentPermissionFailed);
-  }
-}
-
-void ParentPermissionDialog::OnExtensionIconLoaded(const gfx::Image& image) {
-  // The order of preference for the icon to use is:
-  //  1. Icon loaded from extension, if not empty.
-  //  2. Icon passed in params, if not empty.
-  //  3. Default Icon.
-  if (!image.IsEmpty()) {
-    // Use the image that was loaded from the extension if it's not empty
-    icon_ = *image.ToImageSkia();
-  } else if (icon_.isNull()) {
-    // If the params icon is empty, use a default icon.:
-    icon_ = GetDefaultIconBitmapForMaxScaleFactor(extension_->is_app());
-  }
-
-  ShowPromptInternal(false /* show_password_incorrect */);
-}
-
-void ParentPermissionDialog::LoadExtensionIcon() {
-  extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
-      extension_, extension_misc::EXTENSION_ICON_LARGE,
-      ExtensionIconSet::MATCH_BIGGER);
-
-  // Load the image asynchronously. The response will be sent to
-  // OnExtensionIconLoaded.
-  extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile_);
-
-  std::vector<extensions::ImageLoader::ImageRepresentation> images_list;
-  images_list.push_back(extensions::ImageLoader::ImageRepresentation(
-      image, extensions::ImageLoader::ImageRepresentation::NEVER_RESIZE,
-      gfx::Size(),
-      ui::GetScaleFactorForNativeView(web_contents_->GetNativeView())));
-  loader->LoadImagesAsync(
-      extension_, images_list,
-      base::BindOnce(&ParentPermissionDialog::OnExtensionIconLoaded,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void ParentPermissionDialog::ShowPromptInternal(bool show_password_incorrect) {
-  close_dialog_view_callback_ = ShowParentPermissionDialog(
-      profile_, web_contents_->GetTopLevelNativeWindow(),
-      parent_permission_email_addresses_, show_password_incorrect, icon_,
-      message_, extension_,
-      base::BindOnce(&ParentPermissionDialog::OnParentPermissionPromptDone,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void ParentPermissionDialog::OnParentPermissionPromptDone(
-    internal::ParentPermissionDialogViewResult result) {
-  if (result.status ==
-      internal::ParentPermissionDialogViewResult::Status::kAccepted) {
-    HandleParentPermissionDialogAccepted(result);
-  } else {
-    SendResult(Result::kParentPermissionCanceled);
-  }
-}
-
-void ParentPermissionDialog::HandleParentPermissionDialogAccepted(
-    internal::ParentPermissionDialogViewResult result) {
-  std::string parent_obfuscated_gaia_id =
-      GetParentObfuscatedGaiaID(result.selected_parent_permission_email);
-  std::string parent_credential =
-      base::UTF16ToUTF8(result.parent_permission_credential);
-  StartReAuthAccessTokenFetch(parent_obfuscated_gaia_id, parent_credential);
-}
-
-std::string ParentPermissionDialog::GetParentObfuscatedGaiaID(
-    const base::string16& parent_email) const {
-  SupervisedUserService* service =
-      SupervisedUserServiceFactory::GetForProfile(profile_);
-
-  if (service->GetCustodianEmailAddress() == base::UTF16ToUTF8(parent_email))
-    return service->GetCustodianObfuscatedGaiaId();
-
-  if (service->GetSecondCustodianEmailAddress() ==
-      base::UTF16ToUTF8(parent_email)) {
-    return service->GetSecondCustodianObfuscatedGaiaId();
-  }
-
-  NOTREACHED()
-      << "Tried to get obfuscated gaia id for a non-custodian email address";
-  return "";
-}
-
-void ParentPermissionDialog::StartReAuthAccessTokenFetch(
-    const std::string& parent_obfuscated_gaia_id,
-    const std::string& parent_credential) {
-  // The first step of ReAuth is to fetch an OAuth2 access token for the
-  // Reauth API scope.
-  if (test_identity_manager)
-    identity_manager_ = test_identity_manager;
-  else
-    identity_manager_ = IdentityManagerFactory::GetForProfile(profile_);
-
-  signin::ScopeSet scopes;
-  scopes.insert(GaiaConstants::kAccountsReauthOAuth2Scope);
-  DCHECK(!oauth2_access_token_fetcher_);
-  oauth2_access_token_fetcher_ =
-      identity_manager_->CreateAccessTokenFetcherForAccount(
-          identity_manager_->GetPrimaryAccountId(),
-          "chrome_webstore_private_api", scopes,
-          base::BindOnce(&ParentPermissionDialog::OnAccessTokenFetchComplete,
-                         weak_factory_.GetWeakPtr(), parent_obfuscated_gaia_id,
-                         parent_credential),
-          signin::AccessTokenFetcher::Mode::kImmediate);
-}
-
-void ParentPermissionDialog::OnAccessTokenFetchComplete(
-    const std::string& parent_obfuscated_gaia_id,
-    const std::string& parent_credential,
-    GoogleServiceAuthError error,
-    signin::AccessTokenInfo access_token_info) {
-  oauth2_access_token_fetcher_.reset();
-  if (error.state() != GoogleServiceAuthError::NONE) {
-    SendResult(Result::kParentPermissionFailed);
-    return;
-  }
-
-  // Now that we have the OAuth2 access token, we use it when we attempt
-  // to fetch the ReAuthProof token (RAPT) for the parent.
-  StartParentReAuthProofTokenFetch(
-      access_token_info.token, parent_obfuscated_gaia_id, parent_credential);
-}
-
-void ParentPermissionDialog::StartParentReAuthProofTokenFetch(
-    const std::string& child_access_token,
-    const std::string& parent_obfuscated_gaia_id,
-    const std::string& credential) {
-  reauth_token_fetcher_ = std::make_unique<GaiaAuthFetcher>(
-      this, gaia::GaiaSource::kChromeOS, profile_->GetURLLoaderFactory());
-  reauth_token_fetcher_->StartCreateReAuthProofTokenForParent(
-      child_access_token, parent_obfuscated_gaia_id, credential);
-}
-
-void ParentPermissionDialog::SendResult(Result result) {
-  std::move(callback_).Run(result);
-}
-
-void ParentPermissionDialog::OnReAuthProofTokenSuccess(
-    const std::string& reauth_proof_token) {
-  SendResult(Result::kParentPermissionReceived);
-}
-
-void ParentPermissionDialog::OnReAuthProofTokenFailure(
-    const GaiaAuthConsumer::ReAuthProofTokenStatus error) {
-  reauth_token_fetcher_.reset();
-  if (error == GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant) {
-    // Signal to tests that invalid credential was received.
-    invalid_credential_received_ = true;
-
-    // If invalid password was entered, and the dialog is configured to
-    // re-prompt  show the dialog again with the invalid password error message.
-    // prompt again, this time with a password error message.
-    if (reprompt_after_incorrect_credential_)
-      ShowPromptInternal(true /* show password incorrect */);
-    else
-      SendResult(Result::kParentPermissionFailed);  // Fail immediately if not
-                                                    // reprompting.
-
-  } else {
-    SendResult(Result::kParentPermissionFailed);
-  }
-}
diff --git a/chrome/browser/ui/supervised_user/parent_permission_dialog.h b/chrome/browser/ui/supervised_user/parent_permission_dialog.h
index 8cdc5ee2..77ad02a 100644
--- a/chrome/browser/ui/supervised_user/parent_permission_dialog.h
+++ b/chrome/browser/ui/supervised_user/parent_permission_dialog.h
@@ -7,27 +7,16 @@
 
 #include <stddef.h>
 
-#include <memory>
 #include <string>
-#include <vector>
 
 #include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
 #include "base/strings/string16.h"
-#include "google_apis/gaia/gaia_auth_consumer.h"
-#include "google_apis/gaia/gaia_auth_fetcher.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/native_widget_types.h"
 
-class GoogleServiceAuthError;
 class Profile;
 
-namespace signin {
-class AccessTokenFetcher;
-struct AccessTokenInfo;
-class IdentityManager;
-}  // namespace signin
-
 namespace content {
 class WebContents;
 }
@@ -36,205 +25,84 @@
 class Extension;
 }
 
-namespace gfx {
-class Image;
-}
-
-namespace internal {
-struct ParentPermissionDialogViewResult;
-}
-
-// ParentPermissionDialog provides a dialog that will prompt a child user's
-// parent(s) for their permission for action.  The parent(s) approve the action
-// by entering their Google password, which is then verified using the Google
+// This file provides an API that will prompt a child user's parent(s) for their
+// permission for action.  The parent(s) can approve the action by entering
+// their Google password, which is then verified using the Google
 // Reauthentication API's child to parent delegation mode.  The prompt can only
 // be shown if the user is a child.  Otherwise, the prompt will fail.
-//
+
 // Clients should provide a ParentPermissionDialog::DoneCallback to
 // receive the results of the dialog.
 // Example Usage:
 // ParentPermissionDialog::DoneCallback callback = base::BindOnce(
-//            &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
+//            &MyClass::OnParentPermissionDialogDone,
 //            weak_ptr_factory_.GetWeakPtr()))
-// ParentPermissionDialog dialog(profile, std::move(callback));
-// SkBitmap icon = LoadMyIcon();
-// dialog.ShowPrompt(web_contents, "Allow your child to purchase X?", icon);
+// gfx::ImageSkia icon = LoadMyIcon();
 //
+// std::unique_ptr<ParentPermissionDialog> dialog =
+// CreateParentPermissionDialog(profile, window, icon, message, done_callback);
 //
-// This dialog is currently used to display content relevant for a parent to
-// provide permission for the installation of an extension. Using the
-// ShowPromptForExtensionInstallation() method below.
+// dialog->ShowDialog();
 //
-// This class is not thread safe.
-class ParentPermissionDialog : public GaiaAuthConsumer {
+// MyClass::ParentPermissionDialogDone(ParentPermissionDialog::Result result) {
+//   switch (result) {
+//     ...
+//   }
+// }
+
+// API for the Dialog.
+class ParentPermissionDialog {
  public:
   enum class Result {
+    // The parent has given their permission for the action.
     kParentPermissionReceived,
+    // The dialog was canceled.
     kParentPermissionCanceled,
+    // Parent Permission was attempted, but failed due to an unrecoverable
+    // error,  i.e. a network error.
+    // NOTE: This does not indicate that the password entered was incorrect.
     kParentPermissionFailed,
   };
 
+  virtual ~ParentPermissionDialog() = default;
+
+  // Shows the Dialog. The process to show it can be asynchronous, so the dialog
+  // may not appear immediately.
+  virtual void ShowDialog() = 0;
+
+  // Type of the callback invoked with the dialog completes.
   using DoneCallback = base::OnceCallback<void(Result result)>;
 
-  explicit ParentPermissionDialog(Profile* profile);
-
-  ParentPermissionDialog(const ParentPermissionDialog&) = delete;
-  ParentPermissionDialog& operator=(const ParentPermissionDialog&) = delete;
-
-  ~ParentPermissionDialog() override;
-
-  // Shows the Parent Permission Dialog.
-  // |message| specifies the text to be shown in the dialog.
-  // |icon| specifies the icon to be displayed. It can be empty.
-  void ShowPrompt(content::WebContents* web_contents,
-                  const base::string16& message,
-                  const SkBitmap& icon,
-                  DoneCallback callback);
-
-  // Shows the Parent Permission Dialog for the specified extension
-  // installation. The dialog's message will be generated from the extension
-  // itself. |fallback_icon| can be empty.  If it is set, it will be used as a
-  // backup in the event that the extension's icon couldn't be loaded from the
-  // extension itself. If it is empty, and the icon couldn't be loaded from the
-  // extension, a default generic extension icon will be displayed.
-  void ShowPromptForExtensionInstallation(
+  // Creates a ParentPermissionDialog.
+  // |profile| is the child user's profile.
+  // |web_contents| is the web_contents of the initiator.
+  // |window| is the window to which the dialog will be modal.
+  // |icon| will be displayed to the side of |message|.
+  // |message| will be displayed in the body of the dialog.
+  // |done_callback| will be called  on dialog completion.
+  static std::unique_ptr<ParentPermissionDialog> CreateParentPermissionDialog(
+      Profile* profile,
       content::WebContents* web_contents,
+      gfx::NativeWindow window,
+      const gfx::ImageSkia& icon,
+      const base::string16& message,
+      ParentPermissionDialog::DoneCallback done_callback);
+
+  // Creates a ParentPermissionDialog customized for the installation of the
+  // specified |extension|.
+  // |profile| is the child user's profile.
+  // |web_contents| is the web_contents of the initiator.
+  // |window| is the window to which the dialog will be modal.
+  // |icon| will be used as a backup in case |extension| doesn't have a loaded
+  // |done_callback| will be called  on dialog completion.
+  static std::unique_ptr<ParentPermissionDialog>
+  CreateParentPermissionDialogForExtension(
+      Profile* profile,
+      content::WebContents* web_contents,
+      gfx::NativeWindow window,
+      const gfx::ImageSkia& icon,
       const extensions::Extension* extension,
-      const SkBitmap& fallback_icon,
-      DoneCallback callback);
-
-  // Sets whether the prompt is shown again automatically after an
-  // incorrect credential.  This defaults to true, and is only disabled for
-  // testing.  Without this, the test will infinitely repeatedly re-prompt
-  // for a password when it is incorrect.
-  void set_reprompt_after_incorrect_credential(
-      bool reprompt_after_incorrect_credential) {
-    reprompt_after_incorrect_credential_ = reprompt_after_incorrect_credential;
-  }
-
-  static void SetFakeIdentityManagerForTesting(
-      signin::IdentityManager* identity_manager);
-
-  // Only used for testing. Returns true if an invalid credential was received.
-  bool CredentialWasInvalid() const;
-
- private:
-  void LoadParentEmailAddresses();
-  void OnExtensionIconLoaded(const gfx::Image& image);
-  void LoadExtensionIcon();
-
-  // Shows prompt internally.  If |show_password_incorrect| is true, a message
-  // will be displayed indicating that.
-  void ShowPromptInternal(bool show_password_incorrect);
-
-  // Called when the parent permission prompt UI finishes, but before the
-  // ReAuth process starts.
-  void OnParentPermissionPromptDone(
-      internal::ParentPermissionDialogViewResult result);
-
-  // Called to handle the case when a user clicks the Accept button in the
-  // dialog.
-  void HandleParentPermissionDialogAccepted(
-      internal::ParentPermissionDialogViewResult result);
-
-  // Given an email address of the child's parent, return the parents'
-  // obfuscated gaia id.
-  std::string GetParentObfuscatedGaiaID(
-      const base::string16& parent_email) const;
-
-  // Starts the Reauth-scoped OAuth access token fetch process.
-  void StartReAuthAccessTokenFetch(const std::string& parent_obfuscated_gaia_id,
-                                   const std::string& parent_credential);
-
-  // Handles the result of the access token
-  void OnAccessTokenFetchComplete(const std::string& parent_obfuscated_gaia_id,
-                                  const std::string& parent_credential,
-                                  GoogleServiceAuthError error,
-                                  signin::AccessTokenInfo access_token_info);
-
-  // Starts the Parent Reauth proof token fetch process.
-  void StartParentReAuthProofTokenFetch(
-      const std::string& child_access_token,
-      const std::string& parent_obfuscated_gaia_id,
-      const std::string& credential);
-  void SendResult(Result result);
-
-  // GaiaAuthConsumer
-  void OnReAuthProofTokenSuccess(
-      const std::string& reauth_proof_token) override;
-  void OnReAuthProofTokenFailure(
-      const GaiaAuthConsumer::ReAuthProofTokenStatus error) override;
-
-  std::vector<base::string16> parent_permission_email_addresses_;
-
-  std::unique_ptr<GaiaAuthFetcher> reauth_token_fetcher_;
-
-  // Used to fetch OAuth2 access tokens.
-  signin::IdentityManager* identity_manager_ = nullptr;
-  std::unique_ptr<signin::AccessTokenFetcher> oauth2_access_token_fetcher_;
-
-  Profile* const profile_;
-  DoneCallback callback_;
-
-  const extensions::Extension* extension_ = nullptr;
-  gfx::ImageSkia icon_;
-  base::string16 message_;
-  content::WebContents* web_contents_ = nullptr;
-
-  // If true, the prompt will be shown again after an incorrect password
-  // is entered.
-  bool reprompt_after_incorrect_credential_ = true;
-
-  // Callback to call to close the underlying dialog view.
-  base::OnceClosure close_dialog_view_callback_;
-
-  bool invalid_credential_received_ = false;
-
-  base::WeakPtrFactory<ParentPermissionDialog> weak_factory_{this};
+      ParentPermissionDialog::DoneCallback done_callback);
 };
 
-// NOTE: DO NOT USE the following code directly. It is an implementation detail
-// of the dialog. Instead use ParentPermissionDialog above.
-
-namespace internal {
-
-// Internal struct use by the view that implements the dialog to
-// communicate the result status of the dialog UI itself.
-struct ParentPermissionDialogViewResult {
- public:
-  enum class Status {
-    kAccepted,
-    kCanceled,
-    kUnknown,
-  };
-
-  Status status = Status::kUnknown;
-  base::string16 selected_parent_permission_email;
-  base::string16 parent_permission_credential;
-};
-
-}  // namespace internal
-
-// Implemented by the platform specific ui code to actually show the dialog.
-// |window| should be the window to which the dialog is modal.  It comes from
-// whatever widget is associated with opening the parent permission dialog.
-// Returns a closure that should be used to close the dialog view if the caller
-// disappears.  If |show_parent_password_incorrect| is set to true, then the
-// dialog will also display a "Password Incorrect" message.
-base::OnceClosure ShowParentPermissionDialog(
-    Profile* profile,
-    gfx::NativeWindow window,
-    const std::vector<base::string16>& parent_permission_email_addresses,
-    bool show_parent_password_incorrect,
-    const gfx::ImageSkia& icon,
-    const base::string16& message,
-    const extensions::Extension* extension,
-    base::OnceCallback<void(internal::ParentPermissionDialogViewResult result)>
-        view_done_callback);
-
-// Only to be used by tests.  Sets the next status returned by the dialog
-// widget.
-void SetAutoConfirmParentPermissionDialogForTest(
-    internal::ParentPermissionDialogViewResult::Status status);
-
 #endif  // CHROME_BROWSER_UI_SUPERVISED_USER_PARENT_PERMISSION_DIALOG_H_
diff --git a/chrome/browser/ui/supervised_user/parent_permission_dialog_browsertest.cc b/chrome/browser/ui/supervised_user/parent_permission_dialog_browsertest.cc
deleted file mode 100644
index d7b6bbc7..0000000
--- a/chrome/browser/ui/supervised_user/parent_permission_dialog_browsertest.cc
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
-
-#include <memory>
-#include <vector>
-
-#include "base/run_loop.h"
-#include "chrome/browser/extensions/extension_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/supervised_user/logged_in_user_mixin.h"
-#include "chrome/browser/supervised_user/supervised_user_features.h"
-#include "chrome/browser/supervised_user/supervised_user_service.h"
-#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/test/base/mixin_based_in_process_browser_test.h"
-#include "components/signin/public/identity_manager/identity_test_environment.h"
-#include "content/public/test/test_utils.h"
-#include "extensions/common/extension_builder.h"
-#include "google_apis/gaia/fake_gaia.h"
-
-// End to end test of ParentPermissionDialog that exercises the dialog's
-// internal logic the orchestrates the parental permission process.
-class ParentPermissionDialogBrowserTest
-    : public MixinBasedInProcessBrowserTest {
- public:
-  ParentPermissionDialogBrowserTest() = default;
-
-  ParentPermissionDialogBrowserTest(const ParentPermissionDialogBrowserTest&) =
-      delete;
-  ParentPermissionDialogBrowserTest& operator=(
-      const ParentPermissionDialogBrowserTest&) = delete;
-
-  void OnParentPermissionDialogDone(base::OnceClosure quit_closure,
-                                    ParentPermissionDialog::Result result) {
-    result_ = result;
-    std::move(quit_closure).Run();
-  }
-
-  void InitializeFamilyData() {
-    // Set up the child user's custodians (AKA parents).
-    ASSERT_TRUE(browser());
-    PrefService* prefs = browser()->profile()->GetPrefs();
-    prefs->SetString(prefs::kSupervisedUserCustodianEmail,
-                     "test_parent_0@google.com");
-    prefs->SetString(prefs::kSupervisedUserCustodianObfuscatedGaiaId,
-                     "239029320");
-
-    prefs->SetString(prefs::kSupervisedUserSecondCustodianEmail,
-                     "test_parent_1@google.com");
-    prefs->SetString(prefs::kSupervisedUserSecondCustodianObfuscatedGaiaId,
-                     "85948533");
-
-    // Set up the identity test environment, which provides fake
-    // OAuth refresh tokens.
-    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
-    identity_test_env_->MakeAccountAvailable(
-        chromeos::FakeGaiaMixin::kFakeUserEmail);
-    identity_test_env_->SetPrimaryAccount(
-        chromeos::FakeGaiaMixin::kFakeUserEmail);
-    identity_test_env_->SetRefreshTokenForPrimaryAccount();
-    identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
-    ParentPermissionDialog::SetFakeIdentityManagerForTesting(
-        identity_test_env_->identity_manager());
-  }
-
-  void SetUpOnMainThread() override {
-    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
-    logged_in_user_mixin_.LogInUser(true /* issue_any_scope_token */);
-    InitializeFamilyData();
-    SupervisedUserService* service =
-        SupervisedUserServiceFactory::GetForProfile(browser()->profile());
-    service->SetSupervisedUserExtensionsMayRequestPermissionsPrefForTesting(
-        true);
-  }
-
-  void SetNextReAuthStatus(
-      const GaiaAuthConsumer::ReAuthProofTokenStatus next_status) {
-    logged_in_user_mixin_.GetFakeGaiaMixin()->fake_gaia()->SetNextReAuthStatus(
-        next_status);
-  }
-
-  void ShowPrompt() {
-    base::RunLoop run_loop;
-
-    parent_permission_dialog_ =
-        std::make_unique<ParentPermissionDialog>(browser()->profile());
-
-    ParentPermissionDialog::DoneCallback callback = base::BindOnce(
-        &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
-        base::Unretained(this), run_loop.QuitClosure());
-
-    parent_permission_dialog_->set_reprompt_after_incorrect_credential(false);
-    SkBitmap icon =
-        *gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
-    parent_permission_dialog_->ShowPrompt(
-        browser()->tab_strip_model()->GetActiveWebContents(),
-        base::UTF8ToUTF16("Test Prompt Message"), icon, std::move(callback));
-    run_loop.Run();
-  }
-
-  void ShowPromptForExtension(
-      scoped_refptr<const extensions::Extension> extension) {
-    base::RunLoop run_loop;
-
-    parent_permission_dialog_ =
-        std::make_unique<ParentPermissionDialog>(browser()->profile());
-
-    ParentPermissionDialog::DoneCallback callback = base::BindOnce(
-        &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
-        base::Unretained(this), run_loop.QuitClosure());
-
-    parent_permission_dialog_->set_reprompt_after_incorrect_credential(false);
-    SkBitmap icon =
-        *gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
-    parent_permission_dialog_->ShowPromptForExtensionInstallation(
-        browser()->tab_strip_model()->GetActiveWebContents(), extension.get(),
-        icon, std::move(callback));
-    run_loop.Run();
-  }
-
-  void CheckResult(ParentPermissionDialog::Result expected) {
-    EXPECT_EQ(result_, expected);
-  }
-
-  void CheckInvalidCredentialWasReceived() {
-    EXPECT_TRUE(parent_permission_dialog_->CredentialWasInvalid());
-  }
-
- private:
-  ParentPermissionDialog::Result result_;
-  std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
-
-  chromeos::LoggedInUserMixin logged_in_user_mixin_{
-      &mixin_host_, chromeos::LoggedInUserMixin::LogInType::kChild,
-      embedded_test_server(), this};
-
-  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
-};
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, PermissionReceived) {
-  SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kAccepted);
-  ShowPrompt();
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
-}
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
-                       PermissionFailedInvalidPassword) {
-  SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kAccepted);
-  ShowPrompt();
-  CheckInvalidCredentialWasReceived();
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
-}
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
-                       PermissionDialogCanceled) {
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kCanceled);
-  ShowPrompt();
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
-}
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
-                       PermissionReceivedForExtension) {
-  SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kAccepted);
-  ShowPromptForExtension(
-      extensions::ExtensionBuilder("test extension").Build());
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
-}
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
-                       PermissionFailedInvalidPasswordForExtension) {
-  SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kAccepted);
-  ShowPromptForExtension(
-      extensions::ExtensionBuilder("test extension").Build());
-  CheckInvalidCredentialWasReceived();
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
-}
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
-                       PermissionDialogCanceledForExtension) {
-  SetAutoConfirmParentPermissionDialogForTest(
-      internal::ParentPermissionDialogViewResult::Status::kCanceled);
-
-  ShowPromptForExtension(
-      extensions::ExtensionBuilder("test extension").Build());
-  CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
-}
diff --git a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
index 825918f..4f56a72 100644
--- a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
+++ b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.cc
@@ -6,6 +6,7 @@
 
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/tabs/tab_group.h"
@@ -18,59 +19,9 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/image/canvas_image_source.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/paint_vector_icon.h"
 #include "ui/native_theme/native_theme.h"
 
-namespace {
-
-// The tab group icon is a circle that reflects the color of the group in the
-// tab strip. Because it's a simple, colored shape, we use a CanvasImageSource
-// to draw the icon directly into the menu, rather than passing in a vector.
-class TabGroupIconImageSource : public gfx::CanvasImageSource {
- public:
-  // Note: This currently matches the size of the empty tab group header, but
-  // it doesn't share the same variable because this icon should remain
-  // constrained to a menu icon size.
-  static constexpr int kIconSize = 14;
-
-  TabGroupIconImageSource(Profile* profile,
-                          const tab_groups::TabGroupVisualData* visual_data);
-  ~TabGroupIconImageSource() override;
-
- private:
-  // gfx::CanvasImageSource overrides:
-  void Draw(gfx::Canvas* canvas) override;
-
-  Profile* profile_;
-  const tab_groups::TabGroupVisualData* visual_data_;
-
-  DISALLOW_COPY_AND_ASSIGN(TabGroupIconImageSource);
-};
-
-TabGroupIconImageSource::TabGroupIconImageSource(
-    Profile* profile,
-    const tab_groups::TabGroupVisualData* visual_data)
-    : CanvasImageSource(gfx::Size(kIconSize, kIconSize)),
-      profile_(profile),
-      visual_data_(visual_data) {}
-
-TabGroupIconImageSource::~TabGroupIconImageSource() = default;
-
-void TabGroupIconImageSource::Draw(gfx::Canvas* canvas) {
-  const ui::ThemeProvider& tp =
-      ThemeService::GetThemeProviderForProfile(profile_);
-  const SkColor color =
-      tp.GetColor(GetTabGroupContextMenuColorId(visual_data_->color()));
-
-  cc::PaintFlags flags;
-  flags.setStyle(cc::PaintFlags::kFill_Style);
-  flags.setAntiAlias(true);
-  flags.setColor(color);
-  canvas->DrawCircle(gfx::PointF(kIconSize / 2, kIconSize / 2), kIconSize / 2,
-                     flags);
-}
-
-}  // namespace
-
 constexpr int kFirstCommandIndex =
     TabStripModel::ContextMenuCommand::CommandLast + 1;
 
@@ -85,18 +36,19 @@
 void ExistingTabGroupSubMenuModel::Build() {
   // Start command ids after the parent menu's ids to avoid collisions.
   int group_index = kFirstCommandIndex;
+  const auto& tp = ThemeService::GetThemeProviderForProfile(model_->profile());
   for (tab_groups::TabGroupId group : GetOrderedTabGroups()) {
     if (ShouldShowGroup(model_, context_index_, group)) {
       const TabGroup* tab_group = model_->group_model()->GetTabGroup(group);
       const base::string16 group_title = tab_group->visual_data()->title();
       const base::string16 displayed_title =
           group_title.empty() ? tab_group->GetContentString() : group_title;
-      AddItemWithIcon(
-          group_index, displayed_title,
-          gfx::ImageSkia(std::make_unique<TabGroupIconImageSource>(
-                             model_->profile(), tab_group->visual_data()),
-                         gfx::Size(TabGroupIconImageSource::kIconSize,
-                                   TabGroupIconImageSource::kIconSize)));
+      constexpr int kIconSize = 14;
+      const int color_id =
+          GetTabGroupContextMenuColorId(tab_group->visual_data()->color());
+      AddItemWithIcon(group_index, displayed_title,
+                      gfx::CreateVectorIcon(kTabGroupIcon, kIconSize,
+                                            tp.GetColor(color_id)));
     }
     group_index++;
   }
diff --git a/chrome/browser/ui/views/apps/shaped_app_window_targeter_unittest.cc b/chrome/browser/ui/views/apps/shaped_app_window_targeter_unittest.cc
index 49a62be..c3e73c3 100644
--- a/chrome/browser/ui/views/apps/shaped_app_window_targeter_unittest.cc
+++ b/chrome/browser/ui/views/apps/shaped_app_window_targeter_unittest.cc
@@ -17,7 +17,6 @@
 #include "ui/events/event_utils.h"
 #include "ui/views/controls/webview/webview.h"
 #include "ui/views/test/views_test_base.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/easy_resize_window_targeter.h"
 
 using extensions::AppWindow;
@@ -38,7 +37,6 @@
  protected:
   void SetUp() override {
     views::ViewsTestBase::SetUp();
-    new wm::DefaultActivationClient(root_window());
     widget_ = std::make_unique<views::Widget>();
     views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
     params.remove_standard_frame = true;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index c4e4135..2fcd1eb 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -1784,6 +1784,15 @@
       break;
   }
 
+  if (is_mouse_pressed_ && select_all_on_mouse_release_) {
+    // https://crbug.com/1063161 If the user presses the mouse button down and
+    // begins to type without releasing the mouse button, the subsequent release
+    // will delete any newly typed characters due to the SelectAll happening on
+    // mouse-up. If we detect this state, do the select-all immediately.
+    SelectAllForUserGesture();
+    select_all_on_mouse_release_ = false;
+  }
+
   return HandleEarlyTabActions(event);
 }
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
index 3b389f3..d5d20e7 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
@@ -249,6 +249,11 @@
     return test_api_->GetRenderText()->cursor_enabled();
   }
 
+  ui::MouseEvent CreateMouseEvent(ui::EventType type, const gfx::Point& point) {
+    return ui::MouseEvent(type, point, point, ui::EventTimeForNow(),
+                          ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
+  }
+
  protected:
   Profile* profile() { return profile_.get(); }
   TestingOmniboxEditController* omnibox_edit_controller() {
@@ -697,6 +702,18 @@
   EXPECT_TRUE(omnibox_view()->IsSelectAll());
 }
 
+TEST_F(OmniboxViewViewsTest, SelectAllDuringMouseDown) {
+  omnibox_textfield()->OnMousePressed(
+      CreateMouseEvent(ui::ET_MOUSE_PRESSED, {0, 0}));
+  omnibox_view()->SetUserText(base::ASCIIToUTF16("abc"));
+  ui::KeyEvent event_a(ui::ET_KEY_PRESSED, ui::VKEY_A, 0);
+  EXPECT_FALSE(omnibox_view()->IsSelectAll());
+  omnibox_textfield_view()->OnKeyPressed(event_a);
+  // Normally SelectAll happens after OnMouseRelease. Verifying this happens
+  // during OnKeyPress when the mouse is down.
+  EXPECT_TRUE(omnibox_view()->IsSelectAll());
+}
+
 class OmniboxViewViewsClipboardTest
     : public OmniboxViewViewsTest,
       public ::testing::WithParamInterface<ui::TextEditCommand> {
@@ -833,11 +850,6 @@
            !omnibox_view()->model()->user_input_in_progress();
   }
 
-  ui::MouseEvent CreateMouseEvent(ui::EventType type, const gfx::Point& point) {
-    return ui::MouseEvent(type, point, point, ui::EventTimeForNow(),
-                          ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
-  }
-
   // Gets a point at |x_offset| from the beginning of the RenderText.
   gfx::Point GetPointInTextAtXOffset(int x_offset) {
     gfx::Rect bounds = omnibox_view()->GetRenderText()->display_rect();
diff --git a/chrome/browser/ui/views/parent_permission_dialog_browsertest.cc b/chrome/browser/ui/views/parent_permission_dialog_browsertest.cc
new file mode 100644
index 0000000..7c9429a
--- /dev/null
+++ b/chrome/browser/ui/views/parent_permission_dialog_browsertest.cc
@@ -0,0 +1,302 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/supervised_user/logged_in_user_mixin.h"
+#include "chrome/browser/supervised_user/supervised_user_features.h"
+#include "chrome/browser/supervised_user/supervised_user_service.h"
+#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/views/parent_permission_dialog_view.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/common/extension_builder.h"
+#include "google_apis/gaia/fake_gaia.h"
+
+// End to end test of ParentPermissionDialog that exercises the dialog's
+// internal logic the orchestrates the parental permission process.
+class ParentPermissionDialogBrowserTest
+    : public SupportsTestDialog<MixinBasedInProcessBrowserTest>,
+      public TestParentPermissionDialogViewObserver {
+ public:
+  // The next dialog action to take.
+  enum class NextDialogAction {
+    kCancel,
+    kAccept,
+  };
+
+  ParentPermissionDialogBrowserTest()
+      : TestParentPermissionDialogViewObserver(this) {}
+
+  ParentPermissionDialogBrowserTest(const ParentPermissionDialogBrowserTest&) =
+      delete;
+  ParentPermissionDialogBrowserTest& operator=(
+      const ParentPermissionDialogBrowserTest&) = delete;
+
+  void OnParentPermissionDialogDone(ParentPermissionDialog::Result result) {
+    result_ = result;
+    std::move(on_dialog_done_closure_).Run();
+  }
+
+  // TestBrowserUi
+  void ShowUi(const std::string& name) override {
+    SkBitmap icon =
+        *gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
+    content::WebContents* contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+
+    // Set up a RunLoop with a quit closure to block until
+    // the dialog is shown, which is what this method is supposed
+    // to ensure.
+    base::RunLoop run_loop;
+    dialog_shown_closure_ = run_loop.QuitClosure();
+
+    // These use base::DoNothing because we aren't interested in the dialog's
+    // results. Unlike the other non-TestBrowserUi tests, this test doesn't
+    // block, because that interferes with the widget accounting done by
+    // TestBrowserUi.
+    if (name == "default") {
+      parent_permission_dialog_ =
+          ParentPermissionDialog::CreateParentPermissionDialog(
+              browser()->profile(), contents,
+              contents->GetTopLevelNativeWindow(),
+              gfx::ImageSkia::CreateFrom1xBitmap(icon),
+              base::UTF8ToUTF16("Test prompt message"), base::DoNothing());
+    } else if (name == "extension") {
+      parent_permission_dialog_ =
+          ParentPermissionDialog::CreateParentPermissionDialogForExtension(
+              browser()->profile(), contents,
+              contents->GetTopLevelNativeWindow(),
+              gfx::ImageSkia::CreateFrom1xBitmap(icon), test_extension_.get(),
+              base::DoNothing());
+    }
+    parent_permission_dialog_->ShowDialog();
+    run_loop.Run();
+  }
+
+  // TestParentPermissionDialogViewObserver
+  void OnTestParentPermissionDialogViewCreated(
+      ParentPermissionDialogView* view) override {
+    if (dialog_shown_closure_)
+      std::move(dialog_shown_closure_).Run();
+
+    view_ = view;
+    view_->SetIdentityManagerForTesting(identity_test_env_->identity_manager());
+    view_->SetRepromptAfterIncorrectCredential(false);
+
+    if (next_dialog_action_) {
+      switch (next_dialog_action_.value()) {
+        case NextDialogAction::kCancel:
+          view->CancelDialog();
+          break;
+        case NextDialogAction::kAccept:
+          view->AcceptDialog();
+          break;
+      }
+    }
+  }
+
+  void InitializeFamilyData() {
+    // Set up the child user's custodians (AKA parents).
+    ASSERT_TRUE(browser());
+    PrefService* prefs = browser()->profile()->GetPrefs();
+    prefs->SetString(prefs::kSupervisedUserCustodianEmail,
+                     "test_parent_0@google.com");
+    prefs->SetString(prefs::kSupervisedUserCustodianObfuscatedGaiaId,
+                     "239029320");
+
+    prefs->SetString(prefs::kSupervisedUserSecondCustodianEmail,
+                     "test_parent_1@google.com");
+    prefs->SetString(prefs::kSupervisedUserSecondCustodianObfuscatedGaiaId,
+                     "85948533");
+
+    // Set up the identity test environment, which provides fake
+    // OAuth refresh tokens.
+    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
+    identity_test_env_->MakeAccountAvailable(
+        chromeos::FakeGaiaMixin::kFakeUserEmail);
+    identity_test_env_->SetPrimaryAccount(
+        chromeos::FakeGaiaMixin::kFakeUserEmail);
+    identity_test_env_->SetRefreshTokenForPrimaryAccount();
+    identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
+  }
+
+  void SetUpOnMainThread() override {
+    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
+    test_extension_ = extensions::ExtensionBuilder("test extension").Build();
+    logged_in_user_mixin_.LogInUser(true /* issue_any_scope_token */);
+    InitializeFamilyData();
+    SupervisedUserService* service =
+        SupervisedUserServiceFactory::GetForProfile(browser()->profile());
+    service->SetSupervisedUserExtensionsMayRequestPermissionsPrefForTesting(
+        true);
+  }
+
+  void set_next_reauth_status(
+      const GaiaAuthConsumer::ReAuthProofTokenStatus next_status) {
+    logged_in_user_mixin_.GetFakeGaiaMixin()->fake_gaia()->SetNextReAuthStatus(
+        next_status);
+  }
+
+  void set_next_dialog_action(NextDialogAction action) {
+    next_dialog_action_ = action;
+  }
+
+  // This method will block until the next dialog completing action takes place,
+  // so that the result can be checked.
+  void ShowPrompt() {
+    base::RunLoop run_loop;
+    on_dialog_done_closure_ = run_loop.QuitClosure();
+    ParentPermissionDialog::DoneCallback callback = base::BindOnce(
+        &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
+        base::Unretained(this));
+
+    SkBitmap icon =
+        *gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
+    content::WebContents* contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+
+    parent_permission_dialog_ =
+        ParentPermissionDialog::CreateParentPermissionDialog(
+            browser()->profile(), contents, contents->GetTopLevelNativeWindow(),
+            gfx::ImageSkia::CreateFrom1xBitmap(icon),
+            base::UTF8ToUTF16("Test prompt message"), std::move(callback));
+    parent_permission_dialog_->ShowDialog();
+    run_loop.Run();
+  }
+
+  // This method will block until the next dialog action takes place, so that
+  // the result can be checked.
+  void ShowPromptForExtension() {
+    base::RunLoop run_loop;
+    on_dialog_done_closure_ = run_loop.QuitClosure();
+
+    ParentPermissionDialog::DoneCallback callback = base::BindOnce(
+        &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
+        base::Unretained(this));
+
+    SkBitmap icon =
+        *gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
+
+    content::WebContents* contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+
+    parent_permission_dialog_ =
+        ParentPermissionDialog::CreateParentPermissionDialogForExtension(
+            browser()->profile(), contents, contents->GetTopLevelNativeWindow(),
+            gfx::ImageSkia::CreateFrom1xBitmap(icon), test_extension_.get(),
+            std::move(callback));
+    parent_permission_dialog_->ShowDialog();
+    run_loop.Run();
+  }
+
+  void CheckResult(ParentPermissionDialog::Result expected) {
+    EXPECT_EQ(result_, expected);
+  }
+
+  void CheckInvalidCredentialWasReceived() {
+    EXPECT_TRUE(view_->invalid_credential_received());
+  }
+
+ private:
+  ParentPermissionDialogView* view_ = nullptr;
+  std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
+
+  ParentPermissionDialog::Result result_;
+
+  chromeos::LoggedInUserMixin logged_in_user_mixin_{
+      &mixin_host_, chromeos::LoggedInUserMixin::LogInType::kChild,
+      embedded_test_server(), this};
+
+  // Closure that is triggered once the dialog is shown.
+  base::OnceClosure dialog_shown_closure_;
+
+  // Closure that is triggered once the dialog completes.
+  base::OnceClosure on_dialog_done_closure_;
+
+  scoped_refptr<const extensions::Extension> test_extension_ = nullptr;
+
+  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
+  base::Optional<NextDialogAction> next_dialog_action_;
+};
+
+// Tests that a plain dialog widget is shown using the TestBrowserUi
+// infrastructure.
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, InvokeUi_default) {
+  ShowAndVerifyUi();
+}
+
+// Tests that the extension-parameterized dialog widget is shown using the
+// TestBrowserUi infrastructure.
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, InvokeUi_extension) {
+  ShowAndVerifyUi();
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, PermissionReceived) {
+  set_next_reauth_status(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
+  ShowPrompt();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
+                       PermissionFailedInvalidPassword) {
+  set_next_reauth_status(
+      GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
+  ShowPrompt();
+  CheckInvalidCredentialWasReceived();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
+                       PermissionDialogCanceled) {
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kCancel);
+  ShowPrompt();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
+                       PermissionReceivedForExtension) {
+  set_next_reauth_status(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
+  ShowPrompt();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
+                       PermissionFailedInvalidPasswordForExtension) {
+  set_next_reauth_status(
+      GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
+  ShowPromptForExtension();
+  CheckInvalidCredentialWasReceived();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+}
+
+IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
+                       PermissionDialogCanceledForExtension) {
+  set_next_dialog_action(
+      ParentPermissionDialogBrowserTest::NextDialogAction::kCancel);
+
+  ShowPromptForExtension();
+  CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
+}
diff --git a/chrome/browser/ui/views/parent_permission_dialog_view.cc b/chrome/browser/ui/views/parent_permission_dialog_view.cc
index 5be2d79f..12b91de 100644
--- a/chrome/browser/ui/views/parent_permission_dialog_view.cc
+++ b/chrome/browser/ui/views/parent_permission_dialog_view.cc
@@ -12,6 +12,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/supervised_user/supervised_user_service.h"
+#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -19,13 +22,22 @@
 #include "chrome/browser/ui/views/extensions/extension_permissions_view.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
+#include "components/signin/public/identity_manager/access_token_fetcher.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/scope_set.h"
 #include "components/user_manager/user_manager.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/image_loader.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_icon_set.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/permissions/permission_set.h"
+#include "google_apis/gaia/gaia_constants.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_types.h"
@@ -47,36 +59,28 @@
 namespace {
 constexpr int kSectionPaddingTop = 20;
 
-// Whether to auto confirm the dialog for test.
-bool auto_confirm_dialog_for_test = false;
+// Returns bitmap for the default icon with size equal to the default icon's
+// pixel size under maximal supported scale factor.
+const gfx::ImageSkia& GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
+  return is_app ? extensions::util::GetDefaultAppIcon()
+                : extensions::util::GetDefaultExtensionIcon();
+}
 
-// Status to use for auto-confirmation for test.
-internal::ParentPermissionDialogViewResult::Status
-    auto_confirm_status_for_test =
-        internal::ParentPermissionDialogViewResult::Status::kAccepted;
-
-views::Widget* widget_for_test = nullptr;
+TestParentPermissionDialogViewObserver* test_view_observer = nullptr;
 
 }  // namespace
 
-void SetAutoConfirmParentPermissionDialogForTest(
-    internal::ParentPermissionDialogViewResult::Status status) {
-  auto_confirm_dialog_for_test = true;
-  auto_confirm_status_for_test = status;
-}
-
 // Creates a view for the parent approvals section of the extension info and
 // listens for updates to its controls. The view added to the parent contains a
 // parent email selection drop-down box, and a password entry field.
 class ParentPermissionSection : public views::TextfieldController {
  public:
-  ParentPermissionSection(ParentPermissionDialogView* main_view,
-                          const ParentPermissionDialogView::Params& params,
-                          int available_width)
+  ParentPermissionSection(
+      ParentPermissionDialogView* main_view,
+      const std::vector<base::string16>& parent_permission_email_addresses,
+      int available_width)
       : main_view_(main_view) {
-    const std::vector<base::string16>& parent_email_addresses =
-        params.parent_permission_email_addresses;
-    DCHECK_GT(parent_email_addresses.size(), 0u);
+    DCHECK_GT(parent_permission_email_addresses.size(), 0u);
 
     auto view = std::make_unique<views::View>();
 
@@ -85,7 +89,7 @@
         ChromeLayoutProvider::Get()->GetDistanceMetric(
             views::DISTANCE_RELATED_CONTROL_VERTICAL)));
 
-    if (parent_email_addresses.size() > 1) {
+    if (parent_permission_email_addresses.size() > 1) {
       // If there is more than one parent listed, show radio buttons.
       auto select_parent_label = std::make_unique<views::Label>(
           l10n_util::GetStringUTF16(
@@ -97,7 +101,7 @@
 
       // Add first parent radio button
       auto parent_0_radio_button = std::make_unique<views::RadioButton>(
-          base::string16(parent_email_addresses[0]), 1 /* group */);
+          base::string16(parent_permission_email_addresses[0]), 1 /* group */);
 
       // Add a subscription
       parent_0_subscription_ =
@@ -107,7 +111,7 @@
                 main_view->set_selected_parent_permission_email_address(
                     parent_email);
               },
-              main_view, parent_email_addresses[0]));
+              main_view, parent_permission_email_addresses[0]));
 
       // Select parent 0 by default.
       parent_0_radio_button->SetChecked(true);
@@ -115,7 +119,7 @@
 
       // Add second parent radio button.
       auto parent_1_radio_button = std::make_unique<views::RadioButton>(
-          base::string16(parent_email_addresses[1]), 1 /* group */);
+          base::string16(parent_permission_email_addresses[1]), 1 /* group */);
 
       parent_1_subscription_ =
           parent_1_radio_button->AddCheckedChangedCallback(base::BindRepeating(
@@ -124,17 +128,17 @@
                 main_view->set_selected_parent_permission_email_address(
                     parent_email);
               },
-              main_view, parent_email_addresses[1]));
+              main_view, parent_permission_email_addresses[1]));
 
       view->AddChildView(std::move(parent_1_radio_button));
 
       // Default to first parent in the response.
       main_view_->set_selected_parent_permission_email_address(
-          parent_email_addresses[0]);
+          parent_permission_email_addresses[0]);
     } else {
       // If there is just one parent, show a label with that parent's email.
       auto parent_email_label = std::make_unique<views::Label>(
-          parent_email_addresses[0], CONTEXT_BODY_TEXT_LARGE,
+          parent_permission_email_addresses[0], CONTEXT_BODY_TEXT_LARGE,
           views::style::STYLE_SECONDARY);
       parent_email_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
       parent_email_label->SetMultiLine(true);
@@ -143,7 +147,7 @@
       // Since there is only one parent, just set the output value of selected
       // parent email address here..
       main_view->set_selected_parent_permission_email_address(
-          parent_email_addresses[0]);
+          parent_permission_email_addresses[0]);
     }
 
     // Add the credential input field.
@@ -195,16 +199,41 @@
   ParentPermissionDialogView* main_view_;
 };
 
-// ParentPermissionDialogView::Params
+struct ParentPermissionDialogView::Params {
+  Params();
+  explicit Params(const Params& params);
+  ~Params();
+
+  // The icon to be displayed. Usage depends on whether extension is set.
+  gfx::ImageSkia icon;
+
+  // The message to show. Ignored if extension is set.
+  base::string16 message;
+
+  // An optional extension whose permissions should be displayed
+  const extensions::Extension* extension = nullptr;
+
+  // The user's profile
+  Profile* profile = nullptr;
+
+  // The parent window to this window.
+  gfx::NativeWindow window = nullptr;
+
+  // The web contents that initiated the dialog.
+  content::WebContents* web_contents = nullptr;
+
+  // The callback to call on completion.
+  ParentPermissionDialog::DoneCallback done_callback;
+};
+
 ParentPermissionDialogView::Params::Params() = default;
-ParentPermissionDialogView::Params::Params(const Params& params) = default;
 ParentPermissionDialogView::Params::~Params() = default;
 
 // ParentPermissionDialogView
 ParentPermissionDialogView::ParentPermissionDialogView(
     std::unique_ptr<Params> params,
-    ParentPermissionDialogView::DoneCallback done_callback)
-    : params_(std::move(params)), done_callback_(std::move(done_callback)) {
+    ParentPermissionDialogView::Observer* observer)
+    : params_(std::move(params)), observer_(observer) {
   DialogDelegate::SetDefaultButton(ui::DIALOG_BUTTON_OK);
   DialogDelegate::set_draggable(true);
   DialogDelegate::SetButtonLabel(
@@ -213,9 +242,32 @@
   DialogDelegate::SetButtonLabel(
       ui::DIALOG_BUTTON_CANCEL,
       l10n_util::GetStringUTF16(IDS_PARENT_PERMISSION_PROMPT_CANCEL_BUTTON));
+
+  identity_manager_ = IdentityManagerFactory::GetForProfile(params_->profile);
 }
 
-ParentPermissionDialogView::~ParentPermissionDialogView() = default;
+ParentPermissionDialogView::~ParentPermissionDialogView() {
+  // Let the observer know that this object is being destroyed.
+  if (observer_)
+    observer_->OnParentPermissionDialogViewDestroyed();
+
+  // If the object is being destroyed but the callback hasn't been run, then
+  // this is a failure case.
+  if (params_->done_callback) {
+    std::move(params_->done_callback)
+        .Run(ParentPermissionDialog::Result::kParentPermissionFailed);
+  }
+}
+
+void ParentPermissionDialogView::SetIdentityManagerForTesting(
+    signin::IdentityManager* identity_manager) {
+  identity_manager_ = identity_manager;
+}
+
+void ParentPermissionDialogView::SetRepromptAfterIncorrectCredential(
+    bool reprompt) {
+  reprompt_after_incorrect_credential_ = reprompt;
+}
 
 base::string16 ParentPermissionDialogView::GetActiveUserFirstName() const {
   user_manager::UserManager* manager = user_manager::UserManager::Get();
@@ -254,8 +306,8 @@
   layout->StartRow(views::GridLayout::kFixedSize, kTitleColumnSetId);
 
   // Scale down to icon size, but allow smaller icons (don't scale up).
-  if (!params().icon.isNull()) {
-    const gfx::ImageSkia& image = params().icon;
+  if (!params_->icon.isNull()) {
+    const gfx::ImageSkia& image = params_->icon;
     auto icon = std::make_unique<views::ImageView>();
     gfx::Size size(image.width(), image.height());
     size.SetToMin(gfx::Size(icon_size, icon_size));
@@ -264,9 +316,9 @@
     layout->AddView(std::move(icon));
   }
 
-  DCHECK(!params().message.empty());
+  DCHECK(!params_->message.empty());
   std::unique_ptr<views::Label> message_label =
-      views::BubbleFrameView::CreateDefaultTitleLabel(params().message);
+      views::BubbleFrameView::CreateDefaultTitleLabel(params_->message);
   // Setting the message's preferred size to 0 ensures it won't influence the
   // overall size of the dialog. It will be expanded by GridLayout.
   message_label->SetPreferredSize(gfx::Size(0, 0));
@@ -276,27 +328,25 @@
 }
 
 bool ParentPermissionDialogView::Cancel() {
-  // This can be called multiple times because ParentPermissionDialog
-  // calls a callback pointing to OnDialogCloseClosure(), and if this object
-  // still exists at that time, this method will get called again because
-  // Cancel() is called by default when the dialog is explicitly asked to close.
-  // Therefore, we null check the callback here before trying to use it.
-  if (!done_callback_)
-    return true;
-
-  internal::ParentPermissionDialogViewResult result;
-  result.status = internal::ParentPermissionDialogViewResult::Status::kCanceled;
-  std::move(done_callback_).Run(result);
+  SendResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
   return true;
 }
 
 bool ParentPermissionDialogView::Accept() {
-  internal::ParentPermissionDialogViewResult result;
-  result.status = internal::ParentPermissionDialogViewResult::Status::kAccepted;
-  result.parent_permission_credential = parent_permission_credential_;
-  result.selected_parent_permission_email = selected_parent_permission_email_;
-  std::move(done_callback_).Run(result);
-  return true;
+  // Disable the dialog temporarily while we validate the parent's credentials,
+  // which can take some time because it involves a series of async network
+  // requests.
+  SetEnabled(false);
+  // Clear out the invalid credential label, so that it disappears/reappears to
+  // the user to emphasize that the password check happened again.
+  invalid_credential_label_->SetText(base::string16());
+  std::string parent_obfuscated_gaia_id =
+      GetParentObfuscatedGaiaID(selected_parent_permission_email_);
+  std::string parent_credential =
+      base::UTF16ToUTF8(parent_permission_credential_);
+  StartReauthAccessTokenFetch(parent_obfuscated_gaia_id, parent_credential);
+
+  return false;
 }
 
 bool ParentPermissionDialogView::ShouldShowCloseButton() const {
@@ -304,7 +354,7 @@
 }
 
 base::string16 ParentPermissionDialogView::GetAccessibleWindowTitle() const {
-  return params().message;
+  return params_->message;
 }
 
 ui::ModalType ParentPermissionDialogView::GetModalType() const {
@@ -327,17 +377,17 @@
   const int content_width =
       GetPreferredSize().width() - section_container->GetInsets().width();
 
-  if (params().extension) {
+  if (params_->extension) {
+    // Set up the permissions view.
     if (!prompt_permissions_.permissions.empty()) {
       // Set up the permissions header string.
-      const extensions::Extension* extension = params().extension;
       // Shouldn't be asking for permissions for theme installs.
-      DCHECK(!extension->is_theme());
+      DCHECK(!params_->extension->is_theme());
       base::string16 extension_type;
-      if (extension->is_extension()) {
+      if (params_->extension->is_extension()) {
         extension_type = l10n_util::GetStringUTF16(
             IDS_PARENT_PERMISSION_PROMPT_EXTENSION_TYPE_EXTENSION);
-      } else if (extension->is_app()) {
+      } else if (params_->extension->is_app()) {
         extension_type = l10n_util::GetStringUTF16(
             IDS_PARENT_PERMISSION_PROMPT_EXTENSION_TYPE_APP);
       }
@@ -366,42 +416,60 @@
       // can be arbitrarily long.
       section_container->AddChildView(std::move(permissions_view));
     }
-
-    // Add permissions view to the enclosing scroll view.
-    auto scroll_view = std::make_unique<views::ScrollView>();
-    scroll_view->SetHideHorizontalScrollBar(true);
-    scroll_view->SetContents(std::move(section_container));
-    scroll_view->ClipHeightTo(
-        0, provider->GetDistanceMetric(
-               views::DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
-    AddChildView(std::move(scroll_view));
   }
 
-  // Create the parent approval view, which adds itself
+  // Add section container to the enclosing scroll view.
+  auto scroll_view = std::make_unique<views::ScrollView>();
+  scroll_view->SetHideHorizontalScrollBar(true);
+  scroll_view->SetContents(std::move(section_container));
+  scroll_view->ClipHeightTo(
+      0, provider->GetDistanceMetric(
+             views::DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
+  AddChildView(std::move(scroll_view));
+
+  // Create the parent permission section, which adds itself
   // to the main view.
-  parent_permission_section_ =
-      std::make_unique<ParentPermissionSection>(this, params(), content_width);
+  parent_permission_section_ = std::make_unique<ParentPermissionSection>(
+      this, parent_permission_email_addresses_, content_width);
 
-  // Show the "password incorrect" label if needed.
-  if (params().show_parent_password_incorrect) {
-    auto password_incorrect_label = std::make_unique<views::Label>(
-        l10n_util::GetStringUTF16(
-            IDS_PARENT_PERMISSION_PROMPT_PASSWORD_INCORRECT_LABEL),
-        CONTEXT_BODY_TEXT_LARGE, views::style::STYLE_SECONDARY);
-    password_incorrect_label->SetBorder(views::CreateEmptyBorder(
-        0, content_insets.left(), 0, content_insets.right()));
-    password_incorrect_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    password_incorrect_label->SetMultiLine(true);
-    password_incorrect_label->SetEnabledColor(gfx::kGoogleRed500);
-    password_incorrect_label->SizeToFit(content_width);
-    AddChildView(std::move(password_incorrect_label));
-  }
+  // Add the invalid credential label, which is initially empty,
+  // and hence invisible.  It will be updated if the user enters
+  // an incorrect password.
+  auto invalid_credential_label = std::make_unique<views::Label>(
+      base::UTF8ToUTF16(""), CONTEXT_BODY_TEXT_LARGE,
+      views::style::STYLE_SECONDARY);
+  invalid_credential_label->SetBorder(views::CreateEmptyBorder(
+      0, content_insets.left(), 0, content_insets.right()));
+  invalid_credential_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  invalid_credential_label->SetMultiLine(true);
+  invalid_credential_label->SetEnabledColor(gfx::kGoogleRed500);
+  invalid_credential_label->SizeToFit(content_width);
+
+  // Cache the pointer so we we can update the invalid credential label when we
+  // get an incorrect password.
+  invalid_credential_label_ = invalid_credential_label.get();
+  AddChildView(std::move(invalid_credential_label));
 }
 
 void ParentPermissionDialogView::ShowDialog() {
-  if (params().extension)
-    InitializeExtensionData(params().extension);
-  ShowDialogInternal();
+  if (is_showing_)
+    return;
+
+  is_showing_ = true;
+  LoadParentEmailAddresses();
+
+  if (params_->extension)
+    InitializeExtensionData(params_->extension);
+  else
+    ShowDialogInternal();
+}
+
+void ParentPermissionDialogView::CloseDialog() {
+  CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+}
+
+void ParentPermissionDialogView::RemoveObserver() {
+  observer_ = nullptr;
 }
 
 void ParentPermissionDialogView::ShowDialogInternal() {
@@ -411,34 +479,183 @@
   CreateContents();
   chrome::RecordDialogCreation(chrome::DialogIdentifier::PARENT_PERMISSION);
   views::Widget* widget =
-      constrained_window::CreateBrowserModalDialogViews(this, params().window);
+      constrained_window::CreateBrowserModalDialogViews(this, params_->window);
   widget->Show();
 
-  // If we are in a test, auto-confirm the dialog since we can't click
-  // on it directly.
-  if (auto_confirm_dialog_for_test) {
-    widget_for_test = widget;
-    switch (auto_confirm_status_for_test) {
-      case internal::ParentPermissionDialogViewResult::Status::kCanceled:
-        CancelDialog();
-        break;
-      case internal::ParentPermissionDialogViewResult::Status::kAccepted:
-        AcceptDialog();
-        break;
-      default:
-        NOTREACHED();
-        break;
-    }
+  if (test_view_observer)
+    test_view_observer->OnTestParentPermissionDialogViewCreated(this);
+}
+
+void ParentPermissionDialogView::LoadParentEmailAddresses() {
+  // Get the parents' email addresses.  There can be a max of 2 parent email
+  // addresses, the primary and the secondary.
+  SupervisedUserService* service =
+      SupervisedUserServiceFactory::GetForProfile(params_->profile);
+
+  base::string16 primary_parent_email =
+      base::UTF8ToUTF16(service->GetCustodianEmailAddress());
+  if (!primary_parent_email.empty())
+    parent_permission_email_addresses_.push_back(primary_parent_email);
+
+  base::string16 secondary_parent_email =
+      base::UTF8ToUTF16(service->GetSecondCustodianEmailAddress());
+  if (!secondary_parent_email.empty())
+    parent_permission_email_addresses_.push_back(secondary_parent_email);
+
+  if (parent_permission_email_addresses_.empty()) {
+    // TODO(danan):  Add UMA stat for this failure.
+    // https://crbug.com/1049418
+    SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
   }
 }
 
-void ParentPermissionDialogView::CloseDialogView() {
-  GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+void ParentPermissionDialogView::OnExtensionIconLoaded(
+    const gfx::Image& image) {
+  // The order of preference for the icon to use is:
+  //  1. Icon loaded from extension, if not empty.
+  //  2. Icon passed in params, if not empty.
+  //  3. Default Icon.
+  if (!image.IsEmpty()) {
+    // Use the image that was loaded from the extension if it's not empty
+    params_->icon = *image.ToImageSkia();
+  } else if (params_->icon.isNull()) {
+    // If icon is empty, use a default icon.:
+    params_->icon =
+        GetDefaultIconBitmapForMaxScaleFactor(params_->extension->is_app());
+  }
+
+  ShowDialogInternal();
 }
 
-base::OnceClosure ParentPermissionDialogView::GetCloseDialogClosure() {
-  return base::BindOnce(&ParentPermissionDialogView::CloseDialogView,
-                        weak_factory_.GetWeakPtr());
+void ParentPermissionDialogView::LoadExtensionIcon() {
+  DCHECK(params_->extension);
+  extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
+      params_->extension, extension_misc::EXTENSION_ICON_LARGE,
+      ExtensionIconSet::MATCH_BIGGER);
+
+  // Load the image asynchronously. The response will be sent to
+  // OnExtensionIconLoaded.
+  extensions::ImageLoader* loader =
+      extensions::ImageLoader::Get(params_->profile);
+
+  std::vector<extensions::ImageLoader::ImageRepresentation> images_list;
+  images_list.push_back(extensions::ImageLoader::ImageRepresentation(
+      image, extensions::ImageLoader::ImageRepresentation::NEVER_RESIZE,
+      gfx::Size(),
+      ui::GetScaleFactorForNativeView(params_->web_contents->GetNativeView())));
+
+  loader->LoadImagesAsync(
+      params_->extension, images_list,
+      base::BindOnce(&ParentPermissionDialogView::OnExtensionIconLoaded,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void ParentPermissionDialogView::CloseWithReason(
+    views::Widget::ClosedReason reason) {
+  views::Widget* widget = GetWidget();
+  if (widget) {
+    widget->CloseWithReason(reason);
+  } else {
+    //  The widget has disappeared, so delete this view.
+    delete this;
+  }
+}
+
+std::string ParentPermissionDialogView::GetParentObfuscatedGaiaID(
+    const base::string16& parent_email) const {
+  SupervisedUserService* service =
+      SupervisedUserServiceFactory::GetForProfile(params_->profile);
+
+  if (service->GetCustodianEmailAddress() == base::UTF16ToUTF8(parent_email))
+    return service->GetCustodianObfuscatedGaiaId();
+
+  if (service->GetSecondCustodianEmailAddress() ==
+      base::UTF16ToUTF8(parent_email)) {
+    return service->GetSecondCustodianObfuscatedGaiaId();
+  }
+
+  NOTREACHED()
+      << "Tried to get obfuscated gaia id for a non-custodian email address";
+  return std::string();
+}
+
+void ParentPermissionDialogView::StartReauthAccessTokenFetch(
+    const std::string& parent_obfuscated_gaia_id,
+    const std::string& parent_credential) {
+  // The first step of Reauth is to fetch an OAuth2 access token for the
+  // Reauth API scope.
+  signin::ScopeSet scopes;
+  scopes.insert(GaiaConstants::kAccountsReauthOAuth2Scope);
+  oauth2_access_token_fetcher_ =
+      identity_manager_->CreateAccessTokenFetcherForAccount(
+          identity_manager_->GetPrimaryAccountId(),
+          "chrome_webstore_private_api", scopes,
+          base::BindOnce(
+              &ParentPermissionDialogView::OnAccessTokenFetchComplete,
+              weak_factory_.GetWeakPtr(), parent_obfuscated_gaia_id,
+              parent_credential),
+          signin::AccessTokenFetcher::Mode::kImmediate);
+}
+
+void ParentPermissionDialogView::OnAccessTokenFetchComplete(
+    const std::string& parent_obfuscated_gaia_id,
+    const std::string& parent_credential,
+    GoogleServiceAuthError error,
+    signin::AccessTokenInfo access_token_info) {
+  oauth2_access_token_fetcher_.reset();
+  if (error.state() != GoogleServiceAuthError::NONE) {
+    SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+    CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+    return;
+  }
+
+  // Now that we have the OAuth2 access token, we use it when we attempt
+  // to fetch the ReauthProof token (RAPT) for the parent.
+  StartParentReauthProofTokenFetch(
+      access_token_info.token, parent_obfuscated_gaia_id, parent_credential);
+}
+
+void ParentPermissionDialogView::StartParentReauthProofTokenFetch(
+    const std::string& child_access_token,
+    const std::string& parent_obfuscated_gaia_id,
+    const std::string& credential) {
+  reauth_token_fetcher_ = std::make_unique<GaiaAuthFetcher>(
+      this, gaia::GaiaSource::kChromeOS,
+      params_->profile->GetURLLoaderFactory());
+  reauth_token_fetcher_->StartCreateReAuthProofTokenForParent(
+      child_access_token, parent_obfuscated_gaia_id, credential);
+}
+
+void ParentPermissionDialogView::SendResult(
+    ParentPermissionDialog::Result result) {
+  if (!params_->done_callback)
+    return;
+  std::move(params_->done_callback).Run(result);
+}
+
+void ParentPermissionDialogView::OnReAuthProofTokenSuccess(
+    const std::string& reauth_proof_token) {
+  SendResult(ParentPermissionDialog::Result::kParentPermissionReceived);
+  CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
+}
+
+void ParentPermissionDialogView::OnReAuthProofTokenFailure(
+    const GaiaAuthConsumer::ReAuthProofTokenStatus error) {
+  reauth_token_fetcher_.reset();
+  if (error == GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant) {
+    // If invalid password was entered, and the dialog is configured to
+    // re-prompt  show the dialog again with the invalid password error message.
+    // prompt again, this time with a password error message.
+    invalid_credential_received_ = true;
+    if (reprompt_after_incorrect_credential_) {
+      SetEnabled(true);
+      invalid_credential_label_->SetText(l10n_util::GetStringUTF16(
+          IDS_PARENT_PERMISSION_PROMPT_PASSWORD_INCORRECT_LABEL));
+      return;
+    }
+  }
+  SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
+  CloseWithReason(views::Widget::ClosedReason::kUnspecified);
 }
 
 void ParentPermissionDialogView::InitializeExtensionData(
@@ -448,41 +665,114 @@
   // Load Permissions.
   std::unique_ptr<const extensions::PermissionSet> permissions_to_display =
       extensions::util::GetInstallPromptPermissionSetForExtension(
-          extension.get(), params().profile,
+          extension.get(), params_->profile,
           true /* include_optional_permissions */);
   extensions::Manifest::Type type = extension->GetType();
   prompt_permissions_.LoadFromPermissionSet(permissions_to_display.get(), type);
 
+  // Create the dialog's message using the extension's name.
   params_->message = l10n_util::GetStringFUTF16(
       IDS_PARENT_PERMISSION_PROMPT_GO_GET_A_PARENT_FOR_EXTENSION_LABEL,
       base::UTF8ToUTF16(extension->name()));
+
+  LoadExtensionIcon();
 }
 
-base::OnceClosure ShowParentPermissionDialog(
+class ParentPermissionDialogImpl : public ParentPermissionDialog,
+                                   public ParentPermissionDialogView::Observer {
+ public:
+  // Constructor for a generic ParentPermissionDialogImpl
+  ParentPermissionDialogImpl(
+      std::unique_ptr<ParentPermissionDialogView::Params> params);
+
+  ~ParentPermissionDialogImpl() override;
+
+  // ParentPermissionDialog
+  void ShowDialog() override;
+
+  // ParentPermissionDialogView::Observer
+  void OnParentPermissionDialogViewDestroyed() override;
+
+ private:
+  ParentPermissionDialogView* view_ = nullptr;
+};
+
+ParentPermissionDialogImpl::ParentPermissionDialogImpl(
+    std::unique_ptr<ParentPermissionDialogView::Params> params)
+    : view_(new ParentPermissionDialogView(std::move(params), this)) {}
+
+void ParentPermissionDialogImpl::ShowDialog() {
+  // Ownership of dialog_view is passed to the views system when the dialog is
+  // shown here.  We check for the validity of view_ because in theory it could
+  // disappear from beneath this object before ShowDialog() is called.
+  if (view_)
+    view_->ShowDialog();
+}
+
+ParentPermissionDialogImpl::~ParentPermissionDialogImpl() {
+  // We check for the validity of view_ because in theory it could
+  // disappear from beneath this object before ShowDialog() is called.
+  if (view_) {
+    // Important to remove the observer here, so that we don't try to use it in
+    // the destructor to inform the ParentPermissionDialog, which would cause a
+    // use-after-free.
+    view_->RemoveObserver();
+    view_->CloseDialog();
+  }
+}
+
+void ParentPermissionDialogImpl::OnParentPermissionDialogViewDestroyed() {
+  // The underlying ParentPermissionDialogView has been destroyed.
+  view_ = nullptr;
+}
+
+// static
+std::unique_ptr<ParentPermissionDialog>
+ParentPermissionDialog::CreateParentPermissionDialog(
     Profile* profile,
+    content::WebContents* web_contents,
     gfx::NativeWindow window,
-    const std::vector<base::string16>& parent_permission_email_addresses,
-    bool show_parent_password_incorrect,
     const gfx::ImageSkia& icon,
     const base::string16& message,
-    const extensions::Extension* extension,
-    base::OnceCallback<void(internal::ParentPermissionDialogViewResult result)>
-        view_done_callback) {
+    ParentPermissionDialog::DoneCallback done_callback) {
   auto params = std::make_unique<ParentPermissionDialogView::Params>();
-  params->parent_permission_email_addresses = parent_permission_email_addresses;
-  params->show_parent_password_incorrect = show_parent_password_incorrect;
-  params->extension = extension;
   params->message = message;
   params->icon = icon;
   params->profile = profile;
+  params->web_contents = web_contents;
   params->window = window;
+  params->done_callback = std::move(done_callback);
 
-  ParentPermissionDialogView* dialog_view = new ParentPermissionDialogView(
-      std::move(params), std::move(view_done_callback));
+  return std::make_unique<ParentPermissionDialogImpl>(std::move(params));
+}
 
-  // Ownership of dialog_view is passed to the views system when the dialog is
-  // shown.
-  dialog_view->ShowDialog();
+// static
+std::unique_ptr<ParentPermissionDialog>
+ParentPermissionDialog::CreateParentPermissionDialogForExtension(
+    Profile* profile,
+    content::WebContents* web_contents,
+    gfx::NativeWindow window,
+    const gfx::ImageSkia& icon,
+    const extensions::Extension* extension,
+    ParentPermissionDialog::DoneCallback done_callback) {
+  auto params = std::make_unique<ParentPermissionDialogView::Params>();
+  params->extension = extension;
+  params->icon = icon;
+  params->profile = profile;
+  params->web_contents = web_contents;
+  params->window = window;
+  params->done_callback = std::move(done_callback);
 
-  return dialog_view->GetCloseDialogClosure();
+  return std::make_unique<ParentPermissionDialogImpl>(std::move(params));
+}
+
+TestParentPermissionDialogViewObserver::TestParentPermissionDialogViewObserver(
+    TestParentPermissionDialogViewObserver* observer) {
+  DCHECK(!test_view_observer);
+  test_view_observer = observer;
+}
+
+TestParentPermissionDialogViewObserver::
+    ~TestParentPermissionDialogViewObserver() {
+  test_view_observer = nullptr;
 }
diff --git a/chrome/browser/ui/views/parent_permission_dialog_view.h b/chrome/browser/ui/views/parent_permission_dialog_view.h
index a91b097d..7642ad0 100644
--- a/chrome/browser/ui/views/parent_permission_dialog_view.h
+++ b/chrome/browser/ui/views/parent_permission_dialog_view.h
@@ -11,59 +11,47 @@
 #include "base/macros.h"
 #include "chrome/browser/extensions/install_prompt_permissions.h"
 #include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/view.h"
 
-class Profile;
+class GaiaAuthFetcher;
 
 namespace extensions {
 class Extension;
 }  // namespace extensions
 
+namespace signin {
+class AccessTokenFetcher;
+struct AccessTokenInfo;
+class IdentityManager;
+}  // namespace signin
+
+namespace views {
+class Label;
+}
+
 class ParentPermissionSection;
 
 // Modal dialog that shows a dialog that prompts a parent for permission by
-// asking them to enter their google account credentials.
-class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
+// asking them to enter their google account credentials.  This is created only
+// when the dialog is ready to be shown (after the state has been
+// asynchronously fetched).
+class ParentPermissionDialogView : public views::BubbleDialogDelegateView,
+                                   public GaiaAuthConsumer {
  public:
-  using DoneCallback = base::OnceCallback<void(
-      internal::ParentPermissionDialogViewResult result)>;
-
-  struct Params {
-    Params();
-    explicit Params(const Params& params);
-    ~Params();
-
-    // List of email addresses of parents for whom parent permission for
-    // installation should be requested.  These should be the emails of parent
-    // accounts that are permitted to approve extension installations
-    // for the current child user.
-    std::vector<base::string16> parent_permission_email_addresses;
-
-    // If true, shows a message in the parent permission dialog that the
-    // password entered was incorrect.
-    bool show_parent_password_incorrect = true;
-
-    // The icon to be displayed.
-    gfx::ImageSkia icon;
-
-    // The message to show.
-    base::string16 message;
-
-    // An optional extension whose permissions should be displayed
-    const extensions::Extension* extension = nullptr;
-
-    // The user's profile
-    Profile* profile = nullptr;
-
-    // The parent window to this window.
-    gfx::NativeWindow window = nullptr;
+  class Observer {
+   public:
+    // Tells observers that their references to the view are becoming invalid.
+    virtual void OnParentPermissionDialogViewDestroyed() = 0;
   };
+  struct Params;
 
   ParentPermissionDialogView(std::unique_ptr<Params> params,
-                             DoneCallback done_callback);
+                             Observer* observer);
 
   ~ParentPermissionDialogView() override;
 
@@ -71,9 +59,15 @@
   ParentPermissionDialogView& operator=(const ParentPermissionDialogView&) =
       delete;
 
+  // Closes the dialog.
+  void CloseDialog();
+
   // Shows the parent permission dialog.
   void ShowDialog();
 
+  // Removes the observer reference.
+  void RemoveObserver();
+
   void set_selected_parent_permission_email_address(
       const base::string16& email_address) {
     selected_parent_permission_email_ = email_address;
@@ -82,24 +76,19 @@
   void set_parent_permission_credential(const base::string16& credential) {
     parent_permission_credential_ = credential;
   }
-  void CloseDialogView();
 
-  // TODO(https://crbug.com/1058501):  Instead of doing this with closures, use
-  // an interface shared with the views code whose implementation wraps the
-  // underlying view, and delete the instance of that interface in the dtor of
-  // this class.
-  base::OnceClosure GetCloseDialogClosure();
+  bool invalid_credential_received() { return invalid_credential_received_; }
+  void SetIdentityManagerForTesting(signin::IdentityManager* identity_manager);
+  void SetRepromptAfterIncorrectCredential(bool reprompt);
 
  private:
-  const Params& params() const { return *params_; }
-
   base::string16 GetActiveUserFirstName() const;
 
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
   void AddedToWidget() override;
 
-  // views::DialogDeleate:
+  // views::DialogDelegate:
   bool Cancel() override;
   bool Accept() override;
 
@@ -117,6 +106,41 @@
 
   void ShowDialogInternal();
 
+  void AddInvalidCredentialLabel();
+  void LoadParentEmailAddresses();
+  void OnExtensionIconLoaded(const gfx::Image& image);
+  void LoadExtensionIcon();
+  void CloseWithReason(views::Widget::ClosedReason reason);
+
+  // Given an email address of the child's parent, return the parents'
+  // obfuscated gaia id.
+  std::string GetParentObfuscatedGaiaID(
+      const base::string16& parent_email) const;
+
+  // Starts the Reauth-scoped OAuth access token fetch process.
+  void StartReauthAccessTokenFetch(const std::string& parent_obfuscated_gaia_id,
+                                   const std::string& parent_credential);
+
+  // Handles the result of the access token
+  void OnAccessTokenFetchComplete(const std::string& parent_obfuscated_gaia_id,
+                                  const std::string& parent_credential,
+                                  GoogleServiceAuthError error,
+                                  signin::AccessTokenInfo access_token_info);
+
+  // Starts the Parent Reauth proof token fetch process.
+  void StartParentReauthProofTokenFetch(
+      const std::string& child_access_token,
+      const std::string& parent_obfuscated_gaia_id,
+      const std::string& credential);
+
+  // GaiaAuthConsumer
+  void OnReAuthProofTokenSuccess(
+      const std::string& reauth_proof_token) override;
+  void OnReAuthProofTokenFailure(
+      const GaiaAuthConsumer::ReAuthProofTokenStatus error) override;
+
+  void SendResult(ParentPermissionDialog::Result result);
+
   // Sets the |extension| to be optionally displayed in the dialog.  This
   // causes the view to show several extension properties including the
   // permissions, the icon and the extension name.
@@ -127,17 +151,52 @@
   // if an extension has been set.
   extensions::InstallPromptPermissions prompt_permissions_;
 
+  // The email address of the parents to display in the dialog.
+  std::vector<base::string16> parent_permission_email_addresses_;
+
+  bool reprompt_after_incorrect_credential_ = true;
+
+  // Contains the parent-permission related views widgets.
   std::unique_ptr<ParentPermissionSection> parent_permission_section_;
 
+  views::Label* invalid_credential_label_ = nullptr;
+
+  bool invalid_credential_received_ = false;
+
+  // The currently selected parent email.
   base::string16 selected_parent_permission_email_;
+
+  // The currently entered parent credential.
   base::string16 parent_permission_credential_;
 
-  // Configuration parameters for the prompt.
+  // Parameters for the dialog.
   std::unique_ptr<Params> params_;
 
-  DoneCallback done_callback_;
+  // Used to ensure we don't try to show same dialog twice.
+  bool is_showing_ = false;
+
+  // Used to fetch the Reauth token.
+  std::unique_ptr<GaiaAuthFetcher> reauth_token_fetcher_;
+
+  // Used to fetch OAuth2 access tokens.
+  signin::IdentityManager* identity_manager_ = nullptr;
+  std::unique_ptr<signin::AccessTokenFetcher> oauth2_access_token_fetcher_;
+
+  Observer* observer_;
 
   base::WeakPtrFactory<ParentPermissionDialogView> weak_factory_{this};
 };
 
+// Allows tests to observe the create of the testing instance of
+// ParentPermissionDialogView
+class TestParentPermissionDialogViewObserver {
+ public:
+  // Implementers should pass "this" as constructor argument.
+  TestParentPermissionDialogViewObserver(
+      TestParentPermissionDialogViewObserver* observer);
+  ~TestParentPermissionDialogViewObserver();
+  virtual void OnTestParentPermissionDialogViewCreated(
+      ParentPermissionDialogView* view) = 0;
+};
+
 #endif  // CHROME_BROWSER_UI_VIEWS_PARENT_PERMISSION_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/parent_permission_dialog_view_browsertest.cc b/chrome/browser/ui/views/parent_permission_dialog_view_browsertest.cc
deleted file mode 100644
index bb14f76..0000000
--- a/chrome/browser/ui/views/parent_permission_dialog_view_browsertest.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/parent_permission_dialog_view.h"
-
-#include <memory>
-#include <vector>
-
-#include "chrome/browser/extensions/extension_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
-#include "chrome/browser/ui/test/test_browser_dialog.h"
-#include "chrome/test/base/chrome_test_utils.h"
-#include "components/signin/public/identity_manager/identity_test_environment.h"
-
-// A simple DialogBrowserTest for the ParentPermissionDialogView that just
-// shows the dialog.
-class ParentPermissionDialogViewBrowserTest : public DialogBrowserTest {
- public:
-  ParentPermissionDialogViewBrowserTest() {}
-
-  ParentPermissionDialogViewBrowserTest(
-      const ParentPermissionDialogViewBrowserTest&) = delete;
-  ParentPermissionDialogViewBrowserTest& operator=(
-      const ParentPermissionDialogViewBrowserTest&) = delete;
-
-  void OnParentPermissionPromptDone(
-      internal::ParentPermissionDialogViewResult result) {}
-
-  void ShowUi(const std::string& name) override {
-    std::vector<base::string16> parent_emails =
-        std::vector<base::string16>{base::UTF8ToUTF16("parent1@google.com"),
-                                    base::UTF8ToUTF16("parent2@google.com")};
-
-    gfx::ImageSkia icon = extensions::util::GetDefaultExtensionIcon();
-
-    ShowParentPermissionDialog(
-        browser()->profile(), browser()->window()->GetNativeWindow(),
-        parent_emails, false, icon, base::UTF8ToUTF16("Test Message"), nullptr,
-        base::BindOnce(&ParentPermissionDialogViewBrowserTest::
-                           OnParentPermissionPromptDone,
-                       base::Unretained(this)));
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(ParentPermissionDialogViewBrowserTest,
-                       InvokeUi_default) {
-  ShowAndVerifyUi();
-}
diff --git a/chrome/browser/ui/webui/media/media_feeds_ui.cc b/chrome/browser/ui/webui/media/media_feeds_ui.cc
index 4e73a69..c71607d 100644
--- a/chrome/browser/ui/webui/media/media_feeds_ui.cc
+++ b/chrome/browser/ui/webui/media/media_feeds_ui.cc
@@ -25,6 +25,7 @@
   // Setup the data source behind chrome://media-feeds.
   std::unique_ptr<content::WebUIDataSource> source(
       content::WebUIDataSource::Create(chrome::kChromeUIMediaFeedsHost));
+  source->AddResourcePath("media-data-table.js", IDR_MEDIA_DATA_TABLE_JS);
   source->AddResourcePath("media-feeds.js", IDR_MEDIA_FEEDS_JS);
   source->AddResourcePath(
       "services/media_session/public/mojom/media_session.mojom-lite.js",
diff --git a/chrome/browser/ui/webui/media/media_history_ui.cc b/chrome/browser/ui/webui/media/media_history_ui.cc
index a6e59ce..6f43239 100644
--- a/chrome/browser/ui/webui/media/media_history_ui.cc
+++ b/chrome/browser/ui/webui/media/media_history_ui.cc
@@ -25,6 +25,7 @@
   // Setup the data source behind chrome://media-history.
   std::unique_ptr<content::WebUIDataSource> source(
       content::WebUIDataSource::Create(chrome::kChromeUIMediaHistoryHost));
+  source->AddResourcePath("media-data-table.js", IDR_MEDIA_DATA_TABLE_JS);
   source->AddResourcePath("media-history.js", IDR_MEDIA_HISTORY_JS);
   source->AddResourcePath(
       "services/media_session/public/mojom/media_session.mojom-lite.js",
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index ff99b4e..fd6f8ac 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -59,7 +59,7 @@
 #include "net/base/ip_endpoint.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "printing/backend/print_backend.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 #include "url/gurl.h"
 
 namespace chromeos {
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
index dadec97..82de71c 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
@@ -18,7 +18,7 @@
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chromeos/printing/ppd_provider.h"
 #include "chromeos/printing/printer_configuration.h"
-#include "printing/printer_query_result_chromeos.h"
+#include "printing/printer_query_result.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 
 namespace base {
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index 6b9023e..5ae5a964 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -579,9 +579,8 @@
     }
     break;
   }
-  CallJavascriptFunction("settings.WebsiteUsagePrivateApi.returnUsageTotal",
-                         base::Value(usage_host_), base::Value(usage_string),
-                         base::Value(cookie_string));
+  FireWebUIListener("usage-total-changed", base::Value(usage_host_),
+                    base::Value(usage_string), base::Value(cookie_string));
 }
 
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn
index dfed617c..30b26f88 100644
--- a/chrome/browser/web_applications/components/BUILD.gn
+++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -89,6 +89,8 @@
 
   if (is_mac) {
     sources += [
+      "app_shim_registry_mac.cc",
+      "app_shim_registry_mac.h",
       "web_app_file_handler_registration_mac.cc",
       "web_app_shortcut_mac.h",
       "web_app_shortcut_mac.mm",
@@ -159,7 +161,10 @@
   }
 
   if (is_mac) {
-    sources += [ "web_app_shortcut_mac_unittest.mm" ]
+    sources += [
+      "app_shim_registry_mac_unittest.cc",
+      "web_app_shortcut_mac_unittest.mm",
+    ]
   }
 
   if (is_desktop_linux) {
diff --git a/chrome/browser/apps/app_shim/app_shim_registry_mac.cc b/chrome/browser/web_applications/components/app_shim_registry_mac.cc
similarity index 98%
rename from chrome/browser/apps/app_shim/app_shim_registry_mac.cc
rename to chrome/browser/web_applications/components/app_shim_registry_mac.cc
index c90f105..491b1c8 100644
--- a/chrome/browser/apps/app_shim/app_shim_registry_mac.cc
+++ b/chrome/browser/web_applications/components/app_shim_registry_mac.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
 
 #include <memory>
 #include <utility>
diff --git a/chrome/browser/apps/app_shim/app_shim_registry_mac.h b/chrome/browser/web_applications/components/app_shim_registry_mac.h
similarity index 93%
rename from chrome/browser/apps/app_shim/app_shim_registry_mac.h
rename to chrome/browser/web_applications/components/app_shim_registry_mac.h
index ba4957f..609535b 100644
--- a/chrome/browser/apps/app_shim/app_shim_registry_mac.h
+++ b/chrome/browser/web_applications/components/app_shim_registry_mac.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_REGISTRY_MAC_H_
-#define CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_REGISTRY_MAC_H_
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHIM_REGISTRY_MAC_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHIM_REGISTRY_MAC_H_
 
 #include <set>
 #include <string>
@@ -96,4 +96,4 @@
   base::FilePath override_user_data_dir_;
 };
 
-#endif  // CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_REGISTRY_MAC_H_
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHIM_REGISTRY_MAC_H_
diff --git a/chrome/browser/apps/app_shim/app_shim_registry_mac_unittest.cc b/chrome/browser/web_applications/components/app_shim_registry_mac_unittest.cc
similarity index 97%
rename from chrome/browser/apps/app_shim/app_shim_registry_mac_unittest.cc
rename to chrome/browser/web_applications/components/app_shim_registry_mac_unittest.cc
index ebb4ed57..5d80be34 100644
--- a/chrome/browser/apps/app_shim/app_shim_registry_mac_unittest.cc
+++ b/chrome/browser/web_applications/components/app_shim_registry_mac_unittest.cc
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/apps/app_shim/app_shim_registry_mac.h"
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
 
-#include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/web_applications/components/app_shortcut_manager.cc b/chrome/browser/web_applications/components/app_shortcut_manager.cc
index 442ada3..c866de4 100644
--- a/chrome/browser/web_applications/components/app_shortcut_manager.cc
+++ b/chrome/browser/web_applications/components/app_shortcut_manager.cc
@@ -5,10 +5,16 @@
 #include "chrome/browser/web_applications/components/app_shortcut_manager.h"
 
 #include "base/callback.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/app_shortcut_observer.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
+#if defined(OS_MACOSX)
+#include "chrome/browser/web_applications/components/app_shim_registry_mac.h"
+#endif
+
 namespace web_app {
 
 AppShortcutManager::AppShortcutManager(Profile* profile) : profile_(profile) {}
@@ -25,6 +31,20 @@
 void AppShortcutManager::Start() {
   DCHECK(registrar_);
   app_registrar_observer_.Add(registrar_);
+
+#if defined(OS_MACOSX)
+  // Ensure that all installed apps are included in the AppShimRegistry when the
+  // profile is loaded. This is redundant, because apps are registered when they
+  // are installed. It is necessary, however, because app registration was added
+  // long after app installation launched. This should be removed after shipping
+  // for a few versions (whereupon it may be assumed that most applications have
+  // been registered).
+  std::vector<AppId> app_ids = registrar_->GetAppIds();
+  for (const auto& app_id : app_ids) {
+    AppShimRegistry::Get()->OnAppInstalledForProfile(app_id,
+                                                     profile_->GetPath());
+  }
+#endif
 }
 
 void AppShortcutManager::Shutdown() {
@@ -39,6 +59,12 @@
   observers_.RemoveObserver(observer);
 }
 
+void AppShortcutManager::OnWebAppInstalled(const AppId& app_id) {
+#if defined(OS_MACOSX)
+  AppShimRegistry::Get()->OnAppInstalledForProfile(app_id, profile_->GetPath());
+#endif
+}
+
 void AppShortcutManager::OnWebAppWillBeUninstalled(const AppId& app_id) {
   std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id);
   base::FilePath shortcut_data_dir =
@@ -49,6 +75,28 @@
       std::move(shortcut_info));
 }
 
+void AppShortcutManager::OnWebAppUninstalled(const AppId& app_id) {
+  DeleteSharedAppShims(app_id);
+}
+
+void AppShortcutManager::OnWebAppProfileWillBeDeleted(const AppId& app_id) {
+  DeleteSharedAppShims(app_id);
+}
+
+void AppShortcutManager::DeleteSharedAppShims(const AppId& app_id) {
+#if defined(OS_MACOSX)
+  bool delete_multi_profile_shortcuts =
+      AppShimRegistry::Get()->OnAppUninstalledForProfile(app_id,
+                                                         profile_->GetPath());
+  if (delete_multi_profile_shortcuts) {
+    web_app::internals::GetShortcutIOTaskRunner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&web_app::internals::DeleteMultiProfileShortcutsForApp,
+                       app_id));
+  }
+#endif
+}
+
 bool AppShortcutManager::CanCreateShortcuts() const {
 #if defined(OS_CHROMEOS)
   return false;
diff --git a/chrome/browser/web_applications/components/app_shortcut_manager.h b/chrome/browser/web_applications/components/app_shortcut_manager.h
index f32d13b..acdf8b81 100644
--- a/chrome/browser/web_applications/components/app_shortcut_manager.h
+++ b/chrome/browser/web_applications/components/app_shortcut_manager.h
@@ -45,7 +45,10 @@
   void RemoveObserver(AppShortcutObserver* observer);
 
   // AppRegistrarObserver:
+  void OnWebAppInstalled(const AppId& app_id) override;
   void OnWebAppWillBeUninstalled(const AppId& app_id) override;
+  void OnWebAppUninstalled(const AppId& app_id) override;
+  void OnWebAppProfileWillBeDeleted(const AppId& app_id) override;
 
   // Tells the AppShortcutManager that no shortcuts should actually be written
   // to the disk.
@@ -71,6 +74,7 @@
                                      GetShortcutInfoCallback callback) = 0;
 
  protected:
+  void DeleteSharedAppShims(const AppId& app_id);
   void OnShortcutsCreated(const AppId& app_id,
                           CreateShortcutsCallback callback,
                           bool success);
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 06581cc3..649cc99 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -458,6 +458,7 @@
     Bounds rightArrowBounds;
     boolean isAnimating;
     boolean isOverflow;
+    Bounds[] iconsBoundsInScreen;
   };
 
   // Mapped to HotseatSwipeDescriptor in ash/public/cpp/shelf_ui_info.h.
diff --git a/chrome/common/extensions/chrome_extensions_client_unittest.cc b/chrome/common/extensions/chrome_extensions_client_unittest.cc
index 5f5c47e..04af5469 100644
--- a/chrome/common/extensions/chrome_extensions_client_unittest.cc
+++ b/chrome/common/extensions/chrome_extensions_client_unittest.cc
@@ -65,8 +65,8 @@
   scoped_refptr<Extension> extension2(file_util::LoadExtension(
       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   EXPECT_FALSE(extension2.get());
-  EXPECT_STREQ("Could not load icon 'icon.png' for browser action.",
-               error.c_str());
+  EXPECT_EQ("Could not load icon 'icon.png' specified in 'browser_action'.",
+            error);
 
   // Try to install an extension with a zero-length page action icon file.
   ext_dir = install_dir.AppendASCII("extensions")
@@ -77,8 +77,8 @@
   scoped_refptr<Extension> extension3(file_util::LoadExtension(
       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   EXPECT_FALSE(extension3.get());
-  EXPECT_STREQ("Could not load icon 'icon.png' for page action.",
-               error.c_str());
+  EXPECT_EQ("Could not load icon 'icon.png' specified in 'page_action'.",
+            error);
 }
 
 // Test that the ManifestHandlerRegistry handler map hasn't overflowed.
diff --git a/chrome/common/extensions/manifest_handlers/extension_action_handler.cc b/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
index 0730741..e8be190 100644
--- a/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
@@ -10,7 +10,6 @@
 #include "base/values.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "chrome/common/extensions/extension_constants.h"
-#include "chrome/grit/generated_resources.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/file_util.h"
 #include "extensions/common/image_util.h"
@@ -100,20 +99,18 @@
     const Extension* extension,
     std::string* error,
     std::vector<InstallWarning>* warnings) const {
-  int error_message = 0;
   const ActionInfo* action = ActionInfo::GetPageActionInfo(extension);
-  if (action) {
-    error_message = IDS_EXTENSION_LOAD_ICON_FOR_PAGE_ACTION_FAILED;
-  } else {
+  const char* manifest_key = manifest_keys::kPageAction;
+  if (!action) {
     action = ActionInfo::GetBrowserActionInfo(extension);
-    error_message = IDS_EXTENSION_LOAD_ICON_FOR_BROWSER_ACTION_FAILED;
+    manifest_key = manifest_keys::kBrowserAction;
   }
 
   // Analyze the icons for visibility using the default toolbar color, since
   // the majority of Chrome users don't modify their theme.
   if (action && !action->default_icon.empty() &&
       !file_util::ValidateExtensionIconSet(
-          action->default_icon, extension, error_message,
+          action->default_icon, extension, manifest_key,
           image_util::kDefaultToolbarColor, error)) {
     return false;
   }
diff --git a/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc b/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc
index 82f9012..ef8f999 100644
--- a/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/extension_action_handler_unittest.cc
@@ -55,8 +55,10 @@
       extension_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   file_util::SetReportErrorForInvisibleIconForTesting(false);
   EXPECT_FALSE(extension);
-  EXPECT_EQ("The icon is not sufficiently visible 'invisible_icon.png'.",
-            error);
+  EXPECT_EQ(
+      "Icon 'invisible_icon.png' specified in 'browser_action' is not "
+      "sufficiently visible.",
+      error);
 }
 
 // Tests that an unpacked extension with an invisible page action
@@ -71,8 +73,10 @@
       extension_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   file_util::SetReportErrorForInvisibleIconForTesting(false);
   EXPECT_FALSE(extension);
-  EXPECT_EQ("The icon is not sufficiently visible 'invisible_icon.png'.",
-            error);
+  EXPECT_EQ(
+      "Icon 'invisible_icon.png' specified in 'page_action' is not "
+      "sufficiently visible.",
+      error);
 }
 
 // A parameterized test suite to test each different extension action key
diff --git a/chrome/services/local_search_service/index_impl.cc b/chrome/services/local_search_service/index_impl.cc
index 53d5029..f636953 100644
--- a/chrome/services/local_search_service/index_impl.cc
+++ b/chrome/services/local_search_service/index_impl.cc
@@ -16,7 +16,7 @@
 
 namespace {
 
-using Hits = std::vector<mojom::RangePtr>;
+using Hits = std::vector<local_search_service::Range>;
 
 void TokenizeSearchTags(
     const std::vector<base::string16>& search_tags,
@@ -54,8 +54,10 @@
                          partial_match_penalty_rate)) {
       *relevance_score = match.relevance();
       for (const auto& hit : match.hits()) {
-        mojom::RangePtr range = mojom::Range::New(hit.start(), hit.end());
-        hits->push_back(std::move(range));
+        local_search_service::Range range;
+        range.start = hit.start();
+        range.end = hit.end();
+        hits->push_back(range);
       }
       return true;
     }
@@ -63,13 +65,21 @@
   return false;
 }
 
-// Compares mojom::ResultPtr by |score|.
-bool CompareResultPtr(const mojom::ResultPtr& r1, const mojom::ResultPtr& r2) {
-  return r1->score > r2->score;
+// Compares Results by |score|.
+bool CompareResults(const local_search_service::Result& r1,
+                    const local_search_service::Result& r2) {
+  return r1.score > r2.score;
 }
 
 }  // namespace
 
+local_search_service::Data::Data() = default;
+local_search_service::Data::Data(const Data& data) = default;
+local_search_service::Data::~Data() = default;
+local_search_service::Result::Result() = default;
+local_search_service::Result::Result(const Result& result) = default;
+local_search_service::Result::~Result() = default;
+
 IndexImpl::IndexImpl() = default;
 
 IndexImpl::~IndexImpl() = default;
@@ -79,31 +89,57 @@
 }
 
 void IndexImpl::GetSize(GetSizeCallback callback) {
-  std::move(callback).Run(data_.size());
+  const uint64_t size = GetSize();
+  std::move(callback).Run(size);
+}
+
+uint64_t IndexImpl::GetSize() {
+  return data_.size();
 }
 
 void IndexImpl::AddOrUpdate(std::vector<mojom::DataPtr> data,
                             AddOrUpdateCallback callback) {
-  for (const auto& item : data) {
-    const auto& id = item->id;
-    // Keys shouldn't be empty.
-    if (id.empty())
+  std::vector<local_search_service::Data> data_in;
+  for (const auto& d : data) {
+    if (d->id.empty())
       receivers_.ReportBadMessage("Empty ID in updated data");
 
+    local_search_service::Data d_in;
+    d_in.id = d->id;
+    d_in.search_tags = d->search_tags;
+    data_in.push_back(d_in);
+  }
+
+  AddOrUpdate(data_in);
+  std::move(callback).Run();
+}
+
+void IndexImpl::AddOrUpdate(
+    const std::vector<local_search_service::Data>& data) {
+  for (const auto& item : data) {
+    const auto& id = item.id;
+    DCHECK(!id.empty());
+
     // If a key already exists, it will overwrite earlier data.
     data_[id] = std::vector<std::unique_ptr<TokenizedString>>();
-    TokenizeSearchTags(item->search_tags, &data_[id]);
+    TokenizeSearchTags(item.search_tags, &data_[id]);
   }
-  std::move(callback).Run();
 }
 
 void IndexImpl::Delete(const std::vector<std::string>& ids,
                        DeleteCallback callback) {
-  uint32_t num_deleted = 0u;
   for (const auto& id : ids) {
-    // Keys shouldn't be empty.
     if (id.empty())
       receivers_.ReportBadMessage("Empty ID in deleted data");
+  }
+  const uint32_t num_deleted = Delete(ids);
+  std::move(callback).Run(num_deleted);
+}
+
+uint32_t IndexImpl::Delete(const std::vector<std::string>& ids) {
+  uint32_t num_deleted = 0u;
+  for (const auto& id : ids) {
+    DCHECK(!id.empty());
 
     const auto& it = data_.find(id);
     if (it != data_.end()) {
@@ -112,32 +148,89 @@
       ++num_deleted;
     }
   }
-  std::move(callback).Run(num_deleted);
+  return num_deleted;
 }
 
 void IndexImpl::Find(const base::string16& query,
                      int32_t max_latency_in_ms,
                      int32_t max_results,
                      FindCallback callback) {
-  if (query.empty()) {
-    std::move(callback).Run(mojom::ResponseStatus::EMPTY_QUERY, base::nullopt);
-    return;
+  std::vector<local_search_service::Result> results;
+  const auto response = Find(query, max_latency_in_ms, max_results, &results);
+
+  mojom::ResponseStatus mresponse = mojom::ResponseStatus::UNKNOWN_ERROR;
+  switch (response) {
+    case local_search_service::ResponseStatus::kEmptyQuery:
+      mresponse = mojom::ResponseStatus::EMPTY_QUERY;
+      break;
+    case local_search_service::ResponseStatus::kEmptyIndex:
+      mresponse = mojom::ResponseStatus::EMPTY_INDEX;
+      break;
+    case local_search_service::ResponseStatus::kSuccess:
+      mresponse = mojom::ResponseStatus::SUCCESS;
+      break;
+    default:
+      break;
   }
-  if (data_.empty()) {
-    std::move(callback).Run(mojom::ResponseStatus::EMPTY_INDEX, base::nullopt);
+
+  if (mresponse != mojom::ResponseStatus::SUCCESS) {
+    std::move(callback).Run(mresponse, base::nullopt);
     return;
   }
 
-  std::vector<mojom::ResultPtr> results = GetSearchResults(query);
-  std::move(callback).Run(mojom::ResponseStatus::SUCCESS, std::move(results));
+  std::vector<mojom::ResultPtr> mresults;
+  for (const auto& r : results) {
+    mojom::ResultPtr mr = mojom::Result::New();
+    mr->id = r.id;
+    mr->score = r.score;
+    std::vector<mojom::RangePtr> mhits;
+    for (const auto& hit : r.hits) {
+      mojom::RangePtr range = mojom::Range::New(hit.start, hit.end);
+      mhits.push_back(std::move(range));
+    }
+    mr->hits = std::move(mhits);
+    mresults.push_back(std::move(mr));
+  }
+
+  std::move(callback).Run(mojom::ResponseStatus::SUCCESS, std::move(mresults));
+}
+
+local_search_service::ResponseStatus IndexImpl::Find(
+    const base::string16& query,
+    int32_t max_latency_in_ms,
+    int32_t max_results,
+    std::vector<local_search_service::Result>* results) {
+  DCHECK(results);
+  results->clear();
+  if (query.empty()) {
+    return local_search_service::ResponseStatus::kEmptyQuery;
+  }
+  if (data_.empty()) {
+    return local_search_service::ResponseStatus::kEmptyIndex;
+  }
+
+  *results = GetSearchResults(query);
+  return local_search_service::ResponseStatus::kSuccess;
 }
 
 void IndexImpl::SetSearchParams(mojom::SearchParamsPtr search_params,
                                 SetSearchParamsCallback callback) {
-  search_params_ = std::move(search_params);
+  local_search_service::SearchParams search_params_in;
+  search_params_in.relevance_threshold = search_params->relevance_threshold;
+  search_params_in.partial_match_penalty_rate =
+      search_params->partial_match_penalty_rate;
+  search_params_in.use_prefix_only = search_params->use_prefix_only;
+  search_params_in.use_weighted_ratio = search_params->use_weighted_ratio;
+  search_params_in.use_edit_distance = search_params->use_edit_distance;
+  SetSearchParams(search_params_in);
   std::move(callback).Run();
 }
 
+void IndexImpl::SetSearchParams(
+    const local_search_service::SearchParams& search_params) {
+  search_params_ = search_params;
+}
+
 void IndexImpl::GetSearchParamsForTesting(double* relevance_threshold,
                                           double* partial_match_penalty_rate,
                                           bool* use_prefix_only,
@@ -149,36 +242,36 @@
   DCHECK(use_weighted_ratio);
   DCHECK(use_edit_distance);
 
-  *relevance_threshold = search_params_->relevance_threshold;
-  *partial_match_penalty_rate = search_params_->partial_match_penalty_rate;
-  *use_prefix_only = search_params_->use_prefix_only;
-  *use_weighted_ratio = search_params_->use_weighted_ratio;
-  *use_edit_distance = search_params_->use_edit_distance;
+  *relevance_threshold = search_params_.relevance_threshold;
+  *partial_match_penalty_rate = search_params_.partial_match_penalty_rate;
+  *use_prefix_only = search_params_.use_prefix_only;
+  *use_weighted_ratio = search_params_.use_weighted_ratio;
+  *use_edit_distance = search_params_.use_edit_distance;
 }
 
-std::vector<mojom::ResultPtr> IndexImpl::GetSearchResults(
+std::vector<local_search_service::Result> IndexImpl::GetSearchResults(
     const base::string16& query) const {
-  std::vector<mojom::ResultPtr> results;
+  std::vector<local_search_service::Result> results;
   const TokenizedString tokenized_query(query);
 
   for (const auto& item : data_) {
     double relevance_score = 0.0;
     Hits hits;
     if (IsItemRelevant(
-            tokenized_query, item.second, search_params_->relevance_threshold,
-            search_params_->use_prefix_only, search_params_->use_weighted_ratio,
-            search_params_->use_edit_distance,
-            search_params_->partial_match_penalty_rate, &relevance_score,
+            tokenized_query, item.second, search_params_.relevance_threshold,
+            search_params_.use_prefix_only, search_params_.use_weighted_ratio,
+            search_params_.use_edit_distance,
+            search_params_.partial_match_penalty_rate, &relevance_score,
             &hits)) {
-      mojom::ResultPtr result = mojom::Result::New();
-      result->id = item.first;
-      result->score = relevance_score;
-      result->hits = std::move(hits);
-      results.push_back(std::move(result));
+      local_search_service::Result result;
+      result.id = item.first;
+      result.score = relevance_score;
+      result.hits = hits;
+      results.push_back(result);
     }
   }
 
-  std::sort(results.begin(), results.end(), CompareResultPtr);
+  std::sort(results.begin(), results.end(), CompareResults);
   return results;
 }
 
diff --git a/chrome/services/local_search_service/index_impl.h b/chrome/services/local_search_service/index_impl.h
index 8fa5098..2dc4583 100644
--- a/chrome/services/local_search_service/index_impl.h
+++ b/chrome/services/local_search_service/index_impl.h
@@ -24,10 +24,70 @@
 
 namespace local_search_service {
 
+struct Data {
+  // Identifier of the data item, should be unique across the registry. Clients
+  // will decide what ids to use, they could be paths, urls or any opaque string
+  // identifiers.
+  // Ideally IDs should persist across sessions, but this is not strictly
+  // required now because data is not persisted across sessions.
+  std::string id;
+
+  // Data item will be matched between its search tags and query term.
+  std::vector<base::string16> search_tags;
+  Data();
+  Data(const Data& data);
+  ~Data();
+};
+
+struct SearchParams {
+  double relevance_threshold = 0.3;
+  double partial_match_penalty_rate = 0.9;
+  bool use_prefix_only = false;
+  bool use_weighted_ratio = true;
+  bool use_edit_distance = false;
+};
+
+// A numeric range used to represent the start and end position.
+struct Range {
+  uint32_t start;
+  uint32_t end;
+};
+
+// Result is one item that matches a given query. It contains the id of the item
+// and its matching score.
+struct Result {
+  // Id of the data.
+  std::string id;
+  // Relevance score, in the range of [0,1].
+  double score;
+  // Matching ranges.
+  std::vector<Range> hits;
+
+  Result();
+  Result(const Result& result);
+  ~Result();
+};
+
+// Status of the search attempt.
+// More will be added later.
+enum class ResponseStatus {
+  kUnknownError = 0,
+  // Query is empty.
+  kEmptyQuery = 1,
+  // Index is empty (i.e. no data).
+  kEmptyIndex = 2,
+  // Search operation is successful. But there could be no matching item and
+  // result list is empty.
+  kSuccess = 3
+};
+
 // Actual implementation of a local search service Index.
 // It has a registry of searchable data, which can be updated. It also runs an
 // asynchronous search function to find matching items for a given query, and
 // returns results via a callback.
+// In-process clients can choose to call synchronous versions of these
+// functions.
+// TODO(jiameng): all async calls will be deleted in the next cl.
 class IndexImpl : public mojom::Index {
  public:
   IndexImpl();
@@ -36,21 +96,33 @@
   void BindReceiver(mojo::PendingReceiver<mojom::Index> receiver);
 
   // mojom::Index overrides.
+  // Also included the synchronous versions for in-process clients.
   void GetSize(GetSizeCallback callback) override;
+  uint64_t GetSize();
 
+  // IDs of data should not be empty.
   void AddOrUpdate(std::vector<mojom::DataPtr> data,
                    AddOrUpdateCallback callback) override;
+  void AddOrUpdate(const std::vector<local_search_service::Data>& data);
 
+  // IDs should not be empty.
   void Delete(const std::vector<std::string>& ids,
               DeleteCallback callback) override;
+  uint32_t Delete(const std::vector<std::string>& ids);
 
   void Find(const base::string16& query,
             int32_t max_latency_in_ms,
             int32_t max_results,
             FindCallback callback) override;
+  local_search_service::ResponseStatus Find(
+      const base::string16& query,
+      int32_t max_latency_in_ms,
+      int32_t max_results,
+      std::vector<local_search_service::Result>* results);
 
   void SetSearchParams(mojom::SearchParamsPtr search_params,
                        SetSearchParamsCallback callback) override;
+  void SetSearchParams(const local_search_service::SearchParams& search_params);
 
   void GetSearchParamsForTesting(double* relevance_threshold,
                                  double* partial_match_penalty_rate,
@@ -60,7 +132,7 @@
 
  private:
   // Returns all search results for a given query.
-  std::vector<mojom::ResultPtr> GetSearchResults(
+  std::vector<local_search_service::Result> GetSearchResults(
       const base::string16& query) const;
 
   // A map from key to tokenized search-tags.
@@ -69,7 +141,7 @@
   mojo::ReceiverSet<mojom::Index> receivers_;
 
   // Search parameters.
-  mojom::SearchParamsPtr search_params_ = mojom::SearchParams::New();
+  local_search_service::SearchParams search_params_;
 
   DISALLOW_COPY_AND_ASSIGN(IndexImpl);
 };
diff --git a/chrome/services/local_search_service/local_search_service_impl.cc b/chrome/services/local_search_service/local_search_service_impl.cc
index f205c08..ff3aae5 100644
--- a/chrome/services/local_search_service/local_search_service_impl.cc
+++ b/chrome/services/local_search_service/local_search_service_impl.cc
@@ -24,13 +24,28 @@
 void LocalSearchServiceImpl::GetIndex(
     mojom::LocalSearchService::IndexId index_id,
     mojo::PendingReceiver<mojom::Index> index) {
+  if (index_id != mojom::LocalSearchService::IndexId::CROS_SETTINGS)
+    return;
+
+  auto* it = IndexLookupOrCreate(local_search_service::IndexId::kCrosSettings);
+  it->BindReceiver(std::move(index));
+}
+
+IndexImpl* LocalSearchServiceImpl::GetIndexImpl(
+    local_search_service::IndexId index_id) {
+  return IndexLookupOrCreate(index_id);
+}
+
+IndexImpl* LocalSearchServiceImpl::IndexLookupOrCreate(
+    local_search_service::IndexId index_id) {
   auto it = indices_.find(index_id);
   if (it == indices_.end())
     it = indices_.emplace(index_id, std::make_unique<IndexImpl>()).first;
 
   DCHECK(it != indices_.end());
   DCHECK(it->second);
-  it->second->BindReceiver(std::move(index));
+
+  return it->second.get();
 }
 
 }  // namespace local_search_service
diff --git a/chrome/services/local_search_service/local_search_service_impl.h b/chrome/services/local_search_service/local_search_service_impl.h
index 027aa477..0732879 100644
--- a/chrome/services/local_search_service/local_search_service_impl.h
+++ b/chrome/services/local_search_service/local_search_service_impl.h
@@ -20,9 +20,12 @@
 
 class IndexImpl;
 
+enum class IndexId { kCrosSettings = 0 };
+
 // Actual implementation of LocalSearchService.
 // It creates and owns content-specific Indices. Clients can call it |GetIndex|
 // method to get an Index for a given index id.
+// In-process clients can call |GetIndexImpl| directly.
 class LocalSearchServiceImpl : public mojom::LocalSearchService {
  public:
   LocalSearchServiceImpl();
@@ -34,10 +37,13 @@
   void GetIndex(mojom::LocalSearchService::IndexId index_id,
                 mojo::PendingReceiver<mojom::Index> index) override;
 
+  // Only to be used by in-process clients.
+  IndexImpl* GetIndexImpl(local_search_service::IndexId index_id);
+
  private:
+  IndexImpl* IndexLookupOrCreate(local_search_service::IndexId index_id);
   mojo::ReceiverSet<mojom::LocalSearchService> receivers_;
-  std::map<mojom::LocalSearchService::IndexId, std::unique_ptr<IndexImpl>>
-      indices_;
+  std::map<local_search_service::IndexId, std::unique_ptr<IndexImpl>> indices_;
 
   DISALLOW_COPY_AND_ASSIGN(LocalSearchServiceImpl);
 };
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index e2f68de..ee5a8bcdc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2396,7 +2396,6 @@
         "../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc",
         "../browser/ui/ash/volume_controller_browsertest.cc",
         "../browser/ui/browser_finder_chromeos_browsertest.cc",
-        "../browser/ui/supervised_user/parent_permission_dialog_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
@@ -2416,7 +2415,7 @@
         "../browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc",
         "../browser/ui/views/frame/system_menu_model_builder_browsertest_chromeos.cc",
         "../browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc",
-        "../browser/ui/views/parent_permission_dialog_view_browsertest.cc",
+        "../browser/ui/views/parent_permission_dialog_browsertest.cc",
         "../browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc",
         "../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc",
         "../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc",
@@ -3341,6 +3340,7 @@
     "../browser/resource_coordinator/tab_load_tracker_unittest.cc",
     "../browser/resources_util_unittest.cc",
     "../browser/search/contextual_search_policy_handler_android_unittest.cc",
+    "../browser/tab/state/tab_state_db_unittest.cc",
 
     # TODO(hashimoto): those tests should be componentized and moved to
     # //components:components_unittests, http://crbug.com/527882.
@@ -4486,7 +4486,6 @@
       "../../tools/json_schema_compiler/test/features_generation_unittest.cc",
       "../browser/apps/app_shim/app_shim_host_mac_unittest.cc",
       "../browser/apps/app_shim/app_shim_manager_mac_unittest.cc",
-      "../browser/apps/app_shim/app_shim_registry_mac_unittest.cc",
       "../browser/autocomplete/keyword_extensions_delegate_impl_unittest.cc",
       "../browser/browsing_data/counters/hosted_apps_counter_unittest.cc",
       "../browser/extensions/active_tab_unittest.cc",
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 992a20e6..5029830 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -47,8 +47,11 @@
     "$root_gen_dir/chrome/test/data/webui/cr_elements/iron_list_focus_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/cr_focus_row_behavior_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/mock_controller.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/people_page_sync_page_interactive_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/settings_animated_pages_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/settings_ui_tests.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/sync_test_util.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/test_sync_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_util.m.js",
     "$root_gen_dir/chrome/test/data/webui/test_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/test_store.m.js",
@@ -243,6 +246,7 @@
     "$root_gen_dir/chrome/test/data/webui/settings/people_page_sync_controls_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/people_page_sync_page_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/people_page_test.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/personalization_options_test.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/pref_util_tests.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/prefs_test_cases.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/prefs_tests.m.js",
@@ -272,6 +276,7 @@
     "$root_gen_dir/chrome/test/data/webui/settings/test_lifetime_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_open_window_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_password_manager_proxy.m.js",
+    "$root_gen_dir/chrome/test/data/webui/settings/test_privacy_page_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_profile_info_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_reset_browser_proxy.m.js",
     "$root_gen_dir/chrome/test/data/webui/settings/test_search_engines_browser_proxy.m.js",
diff --git a/chrome/test/data/webui/media/media_feeds_webui_browsertest.js b/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
index 72c293b..c21090f 100644
--- a/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
+++ b/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
@@ -6,6 +6,9 @@
  * @fileoverview Test suite for the Media Feeds WebUI.
  */
 
+const EXAMPLE_URL_1 = 'http://example.com/feed.json';
+
+GEN('#include "chrome/browser/media/history/media_history_keyed_service.h"');
 GEN('#include "chrome/browser/ui/browser.h"');
 GEN('#include "media/base/media_switches.h"');
 
@@ -20,6 +23,94 @@
 
   isAsync: true,
 
+  testGenPreamble: function() {
+    GEN('auto* service =');
+    GEN('  media_history::MediaHistoryKeyedService::Get(');
+    GEN('    browser()->profile());');
+    GEN('service->DiscoverMediaFeed(GURL("' + EXAMPLE_URL_1 + '"));');
+    GEN('auto items = std::vector<media_feeds::mojom::MediaFeedItemPtr>();');
+    GEN('auto item = media_feeds::mojom::MediaFeedItem::New();');
+    GEN('item->name = base::ASCIIToUTF16("The Movie");');
+    GEN('item->type = media_feeds::mojom::MediaFeedItemType::kMovie;');
+    GEN('item->date_published = base::Time::FromDeltaSinceWindowsEpoch(');
+    GEN('  base::TimeDelta::FromMinutes(10));');
+    GEN('item->is_family_friendly = true;');
+    GEN('item->action_status =');
+    GEN('  media_feeds::mojom::MediaFeedItemActionStatus::kPotential;');
+    GEN('item->genre = base::ASCIIToUTF16("test");');
+    GEN('item->duration = base::TimeDelta::FromSeconds(30);');
+    GEN('item->live = media_feeds::mojom::LiveDetails::New();');
+    GEN('item->live->start_time = base::Time::FromDeltaSinceWindowsEpoch(');
+    GEN('  base::TimeDelta::FromMinutes(20));');
+    GEN('item->live->end_time = base::Time::FromDeltaSinceWindowsEpoch(');
+    GEN('  base::TimeDelta::FromMinutes(30));');
+    GEN('item->shown_count = 3;');
+    GEN('item->clicked = true;');
+    GEN('item->author = media_feeds::mojom::Author::New();');
+    GEN('item->author->name = "Media Site";');
+    GEN('item->author->url = GURL("https://www.example.com");');
+    GEN('item->action = media_feeds::mojom::Action::New();');
+    GEN('item->action->start_time = base::TimeDelta::FromSeconds(3);');
+    GEN('item->action->url = GURL("https://www.example.com");');
+    GEN('item->interaction_counters.emplace(');
+    GEN('  media_feeds::mojom::InteractionCounterType::kLike, 10000);');
+    GEN('item->interaction_counters.emplace(');
+    GEN('  media_feeds::mojom::InteractionCounterType::kDislike, 20000);');
+    GEN('item->interaction_counters.emplace(');
+    GEN('  media_feeds::mojom::InteractionCounterType::kWatch, 30000);');
+    GEN('item->content_ratings.push_back(');
+    GEN('  media_feeds::mojom::ContentRating::New("MPAA", "PG-13"));');
+    GEN('item->content_ratings.push_back(');
+    GEN('  media_feeds::mojom::ContentRating::New("agency", "TEST2"));');
+    GEN('item->identifiers.push_back(');
+    GEN('  media_feeds::mojom::Identifier::New(');
+    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST1"));');
+    GEN('item->identifiers.push_back(');
+    GEN('  media_feeds::mojom::Identifier::New(');
+    GEN('    media_feeds::mojom::Identifier::Type::kTMSId, "TEST2"));');
+    GEN('item->tv_episode = media_feeds::mojom::TVEpisode::New();');
+    GEN('item->tv_episode->name = "TV Episode Name";');
+    GEN('item->tv_episode->season_number = 1;');
+    GEN('item->tv_episode->episode_number = 2;');
+    GEN('item->tv_episode->identifiers.push_back(');
+    GEN('  media_feeds::mojom::Identifier::New(');
+    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST3"));');
+    GEN('item->play_next_candidate = ');
+    GEN('    media_feeds::mojom::PlayNextCandidate::New();');
+    GEN('item->play_next_candidate->name = "Next TV Episode Name";');
+    GEN('item->play_next_candidate->season_number = 1;');
+    GEN('item->play_next_candidate->episode_number = 3;');
+    GEN('item->play_next_candidate->duration =');
+    GEN('    base::TimeDelta::FromSeconds(10);');
+    GEN('item->play_next_candidate->action = ');
+    GEN('    media_feeds::mojom::Action::New();');
+    GEN('item->play_next_candidate->action->start_time =');
+    GEN('    base::TimeDelta::FromSeconds(3);');
+    GEN('item->play_next_candidate->action->url = ');
+    GEN('    GURL("https://www.example.com");');
+    GEN('item->play_next_candidate->identifiers.push_back(');
+    GEN('  media_feeds::mojom::Identifier::New(');
+    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST4"));');
+    GEN('media_session::MediaImage image1;');
+    GEN('image1.src = GURL("https://www.example.org/image1.png");');
+    GEN('item->images.push_back(image1);');
+    GEN('media_session::MediaImage image2;');
+    GEN('image2.src = GURL("https://www.example.org/image2.png");');
+    GEN('item->images.push_back(image2);');
+    GEN('items.push_back(std::move(item));');
+    GEN('std::vector<media_session::MediaImage> logos;');
+    GEN('media_session::MediaImage logo1;');
+    GEN('logo1.src = GURL("https://www.example.org/logo1.png");');
+    GEN('logos.push_back(logo1);');
+    GEN('media_session::MediaImage logo2;');
+    GEN('logo2.src = GURL("https://www.example.org/logo2.png");');
+    GEN('logos.push_back(logo2);');
+    GEN('service->StoreMediaFeedFetchResult(');
+    GEN('  1, std::move(items), media_feeds::mojom::FetchResult::kSuccess,');
+    GEN('  base::Time::FromDeltaSinceWindowsEpoch(');
+    GEN('  base::TimeDelta::FromMinutes(40)), logos, "Test Feed");');
+  },
+
   extraLibraries: [
     '//third_party/mocha/mocha.js',
     '//chrome/test/data/webui/mocha_adapter.js',
@@ -32,7 +123,7 @@
   });
 
   test('check feeds table is loaded', function() {
-    let feedHeaders =
+    const feedsHeaders =
         Array.from(document.querySelector('#feed-table-header').children);
 
     assertDeepEquals(
@@ -40,9 +131,95 @@
           'ID', 'Url', 'Display Name', 'Last Discovery Time', 'Last Fetch Time',
           'User Status', 'Last Fetch Result', 'Fetch Failed Count',
           'Cache Expiry Time', 'Last Fetch Item Count',
-          'Last Fetch Play Next Count', 'Last Fetch Content Types', 'Logos'
+          'Last Fetch Play Next Count', 'Last Fetch Content Types', 'Logos',
+          'Actions'
         ],
-        feedHeaders.map(x => x.textContent.trim()));
+        feedsHeaders.map(x => x.textContent.trim()));
+
+    const feedsContents =
+        document.querySelector('#feed-table-body').childNodes[0];
+
+    assertEquals('1', feedsContents.childNodes[0].textContent.trim());
+    assertEquals(EXAMPLE_URL_1, feedsContents.childNodes[1].textContent.trim());
+    assertEquals('Test Feed', feedsContents.childNodes[2].textContent.trim());
+    assertEquals('Auto', feedsContents.childNodes[5].textContent.trim());
+    assertEquals('Success', feedsContents.childNodes[6].textContent.trim());
+    assertEquals('0', feedsContents.childNodes[7].textContent.trim());
+    assertEquals(
+        'Sun Dec 31 1600 16:47:02 GMT-0752 (Pacific Standard Time)',
+        feedsContents.childNodes[8].textContent.trim());
+    assertEquals('1', feedsContents.childNodes[9].textContent.trim());
+    assertEquals('1', feedsContents.childNodes[10].textContent.trim());
+    assertEquals('Movie', feedsContents.childNodes[11].textContent.trim());
+    assertEquals(
+        'https://www.example.org/logo1.pnghttps://www.example.org/logo2.png',
+        feedsContents.childNodes[12].textContent.trim());
+    assertEquals(
+        'Show Contents', feedsContents.childNodes[13].textContent.trim());
+
+    // Click on the show contents button.
+    feedsContents.childNodes[13].firstChild.click();
+
+    return whenFeedTableIsPopulatedForTest().then(() => {
+      assertEquals(
+          EXAMPLE_URL_1, document.querySelector('#current-feed').textContent);
+
+      const feedItemsHeaders = Array.from(
+          document.querySelector('#feed-items-table thead tr').children);
+
+      assertDeepEquals(
+          [
+            'Type', 'Name', 'Author', 'Date Published', 'Family Friendly',
+            'Action Status', 'Action URL', 'Action Start Time (secs)',
+            'Interaction Counters', 'Content Ratings', 'Genre', 'Live Details',
+            'TV Episode', 'Play Next Candidate', 'Identifiers', 'Shown Count',
+            'Clicked', 'Images'
+          ],
+          feedItemsHeaders.map(x => x.textContent.trim()));
+
+      const feedItemsContents =
+          document.querySelector('#feed-items-table tbody').childNodes[0];
+
+      assertEquals('Movie', feedItemsContents.childNodes[0].textContent.trim());
+      assertEquals(
+          'The Movie', feedItemsContents.childNodes[1].textContent.trim());
+      assertEquals(
+          'Media Site', feedItemsContents.childNodes[2].textContent.trim());
+      assertEquals(
+          'Sun Dec 31 1600 16:17:02 GMT-0752 (Pacific Standard Time)',
+          feedItemsContents.childNodes[3].textContent.trim());
+      assertEquals('Yes', feedItemsContents.childNodes[4].textContent.trim());
+      assertEquals(
+          'Potential', feedItemsContents.childNodes[5].textContent.trim());
+      assertEquals(
+          'https://www.example.com/',
+          feedItemsContents.childNodes[6].textContent.trim());
+      assertEquals('3', feedItemsContents.childNodes[7].textContent.trim());
+      assertEquals(
+          'Watch=30000 Like=10000 Dislike=20000',
+          feedItemsContents.childNodes[8].textContent.trim());
+      assertEquals(
+          'MPAA PG-13, agency TEST2',
+          feedItemsContents.childNodes[9].textContent.trim());
+      assertEquals('test', feedItemsContents.childNodes[10].textContent.trim());
+      assertEquals(
+          'Live StartTime=Sun Dec 31 1600 16:27:02 GMT-0752 (Pacific Standard Time) EndTime=Sun Dec 31 1600 16:37:02 GMT-0752 (Pacific Standard Time)',
+          feedItemsContents.childNodes[11].textContent.trim());
+      assertEquals(
+          'TV Episode Name EpisodeNumber=2 SeasonNumber=1 PartnerId=TEST3',
+          feedItemsContents.childNodes[12].textContent.trim());
+      assertEquals(
+          'Next TV Episode Name EpisodeNumber=3 SeasonNumber=1 PartnerId=TEST4 ActionURL=https://www.example.com/ ActionStartTimeSecs=3 DurationSecs=10',
+          feedItemsContents.childNodes[13].textContent.trim());
+      assertEquals(
+          'PartnerId=TEST1 TMSId=TEST2',
+          feedItemsContents.childNodes[14].textContent.trim());
+      assertEquals('3', feedItemsContents.childNodes[15].textContent.trim());
+      assertEquals('Yes', feedItemsContents.childNodes[16].textContent.trim());
+      assertEquals(
+          'https://www.example.org/image1.pnghttps://www.example.org/image2.png',
+          feedItemsContents.childNodes[17].textContent.trim());
+    });
   });
 
   mocha.run();
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index dada96e5..c136149 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -41,6 +41,8 @@
     "people_page_test.js",
     "people_page_sync_controls_test.js",
     "people_page_sync_page_test.js",
+    "people_page_sync_page_interactive_test.js",
+    "personalization_options_test.js",
     "prefs_test_cases.js",
     "prefs_tests.js",
     "pref_util_tests.js",
@@ -72,6 +74,7 @@
     "test_metrics_browser_proxy.js",
     "test_open_window_proxy.js",
     "test_password_manager_proxy.js",
+    "test_privacy_page_browser_proxy.js",
     "test_profile_info_browser_proxy.js",
     "test_reset_browser_proxy.js",
     "test_search_engines_browser_proxy.js",
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 6d56bc20..e04bb67 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -1003,17 +1003,17 @@
 };
 
 TEST_F('CrSettingsPersonalizationOptionsTest', 'AllBuilds', function() {
-  mocha.grep('PersonalizationOptionsTests_AllBuilds').run();
+  runMochaSuite('PersonalizationOptionsTests_AllBuilds');
 });
 
 GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 TEST_F('CrSettingsPersonalizationOptionsTest', 'OfficialBuild', function() {
-  mocha.grep('PersonalizationOptionsTests_OfficialBuild').run();
+  runMochaSuite('PersonalizationOptionsTests_OfficialBuild');
 });
 GEN('#endif');
 
 TEST_F('CrSettingsPersonalizationOptionsTest', 'AllBuildsOld', function() {
-  mocha.grep('PersonalizationOptionsTests_AllBuilds_Old').run();
+  runMochaSuite('PersonalizationOptionsTests_AllBuilds_Old');
 });
 
 /**
@@ -1464,13 +1464,13 @@
   __proto__: CrSettingsBrowserTest.prototype,
 
   /** @override */
-  browsePreload: 'chrome://settings/privacy_page/privacy_page.html',
+  browsePreload: 'chrome://settings/site_settings/site_details.html',
 
   /** @override */
   extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
+    '//ui/webui/resources/js/util.js',
     '../test_browser_proxy.js',
     'test_util.js',
-    'ensure_lazy_loaded.js',
     'test_site_settings_prefs_browser_proxy.js',
     'site_details_tests.js',
   ]),
@@ -1654,7 +1654,6 @@
     '../test_browser_proxy.js',
     '../test_util.js',
     'test_util.js',
-    'ensure_lazy_loaded.js',
     'test_site_settings_prefs_browser_proxy.js',
     'site_list_entry_tests.js',
   ]),
diff --git a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
index c778421..0b60424 100644
--- a/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_v3_browsertest.js
@@ -233,6 +233,30 @@
   runMochaSuite('AddExceptionDialog');
 });
 
+
+// eslint-disable-next-line no-var
+var CrSettingsPersonalizationOptionsV3Test =
+    class extends CrSettingsV3BrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://settings/test_loader.html?module=settings/personalization_options_test.m.js';
+  }
+};
+
+TEST_F('CrSettingsPersonalizationOptionsV3Test', 'AllBuilds', function() {
+  runMochaSuite('PersonalizationOptionsTests_AllBuilds');
+});
+
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
+TEST_F('CrSettingsPersonalizationOptionsV3Test', 'OfficialBuild', function() {
+  runMochaSuite('PersonalizationOptionsTests_OfficialBuild');
+});
+GEN('#endif');
+
+TEST_F('CrSettingsPersonalizationOptionsV3Test', 'AllBuildsOld', function() {
+  runMochaSuite('PersonalizationOptionsTests_AllBuilds_Old');
+});
+
 [['AppearanceFontsPage', 'appearance_fonts_page_test.m.js'],
  ['AppearancePage', 'appearance_page_test.m.js'],
  ['AutofillPage', 'autofill_page_test.m.js'],
diff --git a/chrome/test/data/webui/settings/cr_settings_v3_interactive_ui_tests.js b/chrome/test/data/webui/settings/cr_settings_v3_interactive_ui_tests.js
index dceb9b1..2434010 100644
--- a/chrome/test/data/webui/settings/cr_settings_v3_interactive_ui_tests.js
+++ b/chrome/test/data/webui/settings/cr_settings_v3_interactive_ui_tests.js
@@ -38,6 +38,18 @@
 });
 
 // eslint-disable-next-line no-var
+var CrSettingsSyncPageV3Test = class extends CrSettingsV3InteractiveUITest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://settings/test_loader.html?module=settings/people_page_sync_page_interactive_test.m.js';
+  }
+};
+
+TEST_F('CrSettingsSyncPageV3Test', 'All', function() {
+  mocha.run();
+});
+
+// eslint-disable-next-line no-var
 var SettingsUIV3InteractiveTest = class extends CrSettingsV3InteractiveUITest {
   /** @override */
   get browsePreload() {
diff --git a/chrome/test/data/webui/settings/people_page_sync_page_interactive_test.js b/chrome/test/data/webui/settings/people_page_sync_page_interactive_test.js
index 9ff6f69..dae612a 100644
--- a/chrome/test/data/webui/settings/people_page_sync_page_interactive_test.js
+++ b/chrome/test/data/webui/settings/people_page_sync_page_interactive_test.js
@@ -2,6 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {SyncBrowserProxyImpl, Router} from 'chrome://settings/settings.js';
+// #import 'chrome://settings/lazy_load.js';
+// #import {setupRouterWithSyncRoutes} from 'chrome://test/settings/sync_test_util.m.js';
+// #import {TestSyncBrowserProxy} from 'chrome://test/settings/test_sync_browser_proxy.m.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
 suite('sync-page-test', function() {
   /** @type {SyncPageElement} */ let syncPage;
 
@@ -9,7 +18,8 @@
     sync_test_util.setupRouterWithSyncRoutes();
     PolymerTest.clearBody();
     settings.SyncBrowserProxyImpl.instance_ = new TestSyncBrowserProxy();
-    settings.Router.getInstance().navigateTo(settings.routes.SYNC);
+    const router = settings.Router.getInstance();
+    router.navigateTo(router.getRoutes().SYNC);
     syncPage = document.createElement('settings-sync-page');
     document.body.appendChild(syncPage);
     Polymer.dom.flush();
diff --git a/chrome/test/data/webui/settings/personalization_options_test.js b/chrome/test/data/webui/settings/personalization_options_test.js
index 54bba6fd..c900ed0 100644
--- a/chrome/test/data/webui/settings/personalization_options_test.js
+++ b/chrome/test/data/webui/settings/personalization_options_test.js
@@ -2,6 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {PrivacyPageBrowserProxyImpl, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
+// #import 'chrome://settings/lazy_load.js';
+// #import {TestSyncBrowserProxy} from 'chrome://test/settings/test_sync_browser_proxy.m.js';
+// #import {TestPrivacyPageBrowserProxy} from 'chrome://test/settings/test_privacy_page_browser_proxy.m.js';
+// #import {isChromeOS} from 'chrome://resources/js/cr.m.js';
+// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {isVisible, isChildVisible, eventToPromise} from 'chrome://test/test_util.m.js';
+// clang-format on
+
 cr.define('settings_personalization_options', function() {
 
   suite('PersonalizationOptionsTests_AllBuilds', function() {
@@ -258,4 +268,5 @@
       assertTrue(test_util.isChildVisible(testElement, '#linkDoctor'));
     });
   });
+  // #cr_define_end
 });
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.js
index edf96bb..33d87d13 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.js
@@ -2,6 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+class TestWebsiteUsageBrowserProxy extends TestBrowserProxy {
+  constructor() {
+    super(['clearUsage', 'fetchUsageTotal']);
+  }
+
+  /** @override */
+  fetchUsageTotal(host) {
+    this.methodCalled('fetchUsageTotal', host);
+  }
+
+  /** @override */
+  clearUsage(origin) {
+    this.methodCalled('clearUsage', origin);
+  }
+}
+
 /** @fileoverview Suite of tests for site-details. */
 suite('SiteDetails', function() {
   /**
@@ -16,6 +32,18 @@
    */
   let prefs;
 
+  /**
+   * The mock site settings prefs proxy object to use during test.
+   * @type {TestSiteSettingsPrefsBrowserProxy}
+   */
+  let browserProxy;
+
+  /**
+   * The mock website usage proxy object to use during test.
+   * @type {TestWebsiteUsageBrowserProxy}
+   */
+  let websiteUsageProxy;
+
   // Initialize a site-details before each test.
   setup(function() {
     prefs = test_util.createSiteSettingsPrefs([], [
@@ -128,6 +156,9 @@
 
     browserProxy = new TestSiteSettingsPrefsBrowserProxy();
     settings.SiteSettingsPrefsBrowserProxyImpl.instance_ = browserProxy;
+    websiteUsageProxy = new TestWebsiteUsageBrowserProxy();
+    settings.WebsiteUsageBrowserProxyImpl.instance_ = websiteUsageProxy;
+
     PolymerTest.clearBody();
   });
 
@@ -259,42 +290,30 @@
     browserProxy.setPrefs(prefs);
     testElement = createSiteDetails(origin);
 
-    // Remove the current website-usage-private-api element.
-    const parent = testElement.$.usageApi.parentNode;
-    assertTrue(parent != undefined);
-    testElement.$.usageApi.remove();
-
-    // Replace it with a mock version.
-    let usageCleared = false;
-    Polymer({
-      is: 'mock-website-usage-private-api-storage',
-
-      fetchUsageTotal: function(host) {
-        testElement.storedData_ = '1 KB';
-      },
-
-      clearUsage: function(origin, task) {
-        usageCleared = true;
-      },
-    });
-    const api =
-        document.createElement('mock-website-usage-private-api-storage');
-    testElement.$.usageApi = api;
-    parent.appendChild(api);
     Polymer.dom.flush();
 
     // Call onOriginChanged_() manually to simulate a new navigation.
     testElement.currentRouteChanged(settings.Route);
-    return browserProxy.whenCalled('getOriginPermissions').then(() => {
-      // Ensure the mock's methods were called and check usage was cleared on
-      // clicking the trash button.
-      assertEquals('1 KB', testElement.storedData_);
-      assertTrue(testElement.$$('#noStorage').hidden);
-      assertFalse(testElement.$$('#storage').hidden);
+    return Promise
+        .all([
+          browserProxy.whenCalled('getOriginPermissions'),
+          websiteUsageProxy.whenCalled('fetchUsageTotal'),
+        ])
+        .then(results => {
+          const hostRequested = results[1];
+          assertEquals('foo.com', hostRequested);
+          cr.webUIListenerCallback(
+              'usage-total-changed', hostRequested, '1 KB', '10 cookies');
+          assertEquals('1 KB', testElement.storedData_);
+          assertTrue(testElement.$$('#noStorage').hidden);
+          assertFalse(testElement.$$('#storage').hidden);
 
-      testElement.$$('#confirmClearStorage .action-button').click();
-      assertTrue(usageCleared);
-    });
+          testElement.$$('#confirmClearStorage .action-button').click();
+          return websiteUsageProxy.whenCalled('clearUsage');
+        })
+        .then(originCleared => {
+          assertEquals('https://foo.com/', originCleared);
+        });
   });
 
   test('cookies gets deleted properly', function() {
@@ -302,42 +321,30 @@
     browserProxy.setPrefs(prefs);
     testElement = createSiteDetails(origin);
 
-    // Remove the current website-usage-private-api element.
-    const parent = testElement.$.usageApi.parentNode;
-    assertTrue(parent != undefined);
-    testElement.$.usageApi.remove();
-
-    // Replace it with a mock version.
-    let usageCleared = false;
-    Polymer({
-      is: 'mock-website-usage-private-api-cookies',
-
-      fetchUsageTotal: function(host) {
-        testElement.numCookies_ = '10 cookies';
-      },
-
-      clearUsage: function(origin, task) {
-        usageCleared = true;
-      },
-    });
-    const api =
-        document.createElement('mock-website-usage-private-api-cookies');
-    testElement.$.usageApi = api;
-    parent.appendChild(api);
-    Polymer.dom.flush();
-
     // Call onOriginChanged_() manually to simulate a new navigation.
     testElement.currentRouteChanged(settings.Route);
-    return browserProxy.whenCalled('getOriginPermissions').then(() => {
-      // Ensure the mock's methods were called and check usage was cleared on
-      // clicking the trash button.
-      assertEquals('10 cookies', testElement.numCookies_);
-      assertTrue(testElement.$$('#noStorage').hidden);
-      assertFalse(testElement.$$('#storage').hidden);
+    return Promise
+        .all([
+          browserProxy.whenCalled('getOriginPermissions'),
+          websiteUsageProxy.whenCalled('fetchUsageTotal'),
+        ])
+        .then(results => {
+          // Ensure the mock's methods were called and check usage was cleared
+          // on clicking the trash button.
+          const hostRequested = results[1];
+          assertEquals('foo.com', hostRequested);
+          cr.webUIListenerCallback(
+              'usage-total-changed', hostRequested, '1 KB', '10 cookies');
+          assertEquals('10 cookies', testElement.numCookies_);
+          assertTrue(testElement.$$('#noStorage').hidden);
+          assertFalse(testElement.$$('#storage').hidden);
 
-      testElement.$$('#confirmClearStorage .action-button').click();
-      assertTrue(usageCleared);
-    });
+          testElement.$$('#confirmClearStorage .action-button').click();
+          return websiteUsageProxy.whenCalled('clearUsage');
+        })
+        .then(originCleared => {
+          assertEquals('https://foo.com/', originCleared);
+        });
   });
 
   test('correct pref settings are shown', function() {
@@ -431,25 +438,6 @@
   test('show confirmation dialog on clear storage', function() {
     browserProxy.setPrefs(prefs);
     testElement = createSiteDetails('https://foo.com:443');
-
-    // Give |testElement.storedData_| a non-empty value to make the clear
-    // storage button appear. Also replace the website-usage-private-api element
-    // to prevent a call going back to the C++ upon confirming the dialog.
-    const parent = testElement.$.usageApi.parentNode;
-    assertTrue(parent != undefined);
-    testElement.$.usageApi.remove();
-    Polymer({
-      // Use a different mock name here to avoid an error when all tests are run
-      // together as there is no way to unregister a Polymer custom element.
-      is: 'mock1-website-usage-private-api',
-      fetchUsageTotal: function() {
-        testElement.storedData_ = '1 KB';
-      },
-      clearUsage: function(origin) {},
-    });
-    const api = document.createElement('mock1-website-usage-private-api');
-    testElement.$.usageApi = api;
-    parent.appendChild(api);
     Polymer.dom.flush();
 
     // Check both cancelling and accepting the dialog closes it.
diff --git a/chrome/test/data/webui/settings/test_privacy_page_browser_proxy.js b/chrome/test/data/webui/settings/test_privacy_page_browser_proxy.js
index 4d49fac..320551f3 100644
--- a/chrome/test/data/webui/settings/test_privacy_page_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_privacy_page_browser_proxy.js
@@ -2,8 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+// #import {SecureDnsUiManagementMode, SecureDnsMode} from 'chrome://settings/settings.js';
+// clang-format on
+
 /** @implements {settings.PrivacyPageBrowserProxy} */
-class TestPrivacyPageBrowserProxy extends TestBrowserProxy {
+/* #export */ class TestPrivacyPageBrowserProxy extends TestBrowserProxy {
   constructor() {
     super([
       'getMetricsReporting',
diff --git a/chromeos/components/media_app_ui/BUILD.gn b/chromeos/components/media_app_ui/BUILD.gn
index 99ebd37..ed56890 100644
--- a/chromeos/components/media_app_ui/BUILD.gn
+++ b/chromeos/components/media_app_ui/BUILD.gn
@@ -94,6 +94,11 @@
                     "jscomp_error=strictCheckTypes",
                     "jscomp_error=reportUnknownTypes",
                     "language_in=ECMASCRIPT_2018",
+
+                    # TODO(crbug/1048973): Remove these when the mojo bindings
+                    # js is updated to pass a closure compile check.
+                    "hide_warnings_for=mojo/public/js/",
+                    "hide_warnings_for=chromeos/components/media_app_ui/media_app_ui.mojom-lite-for-compile.js",
                   ]
   deps = [
     ":test_driver_api_js",
@@ -129,6 +134,7 @@
   sources = [ "test/driver.js" ]
   deps = [
     ":test_driver_api_js",
+    "//chromeos/components/media_app_ui/resources/js:launch",
     "//chromeos/components/media_app_ui/resources/js:message_pipe",
     "//ui/webui/resources/js:assert",
   ]
diff --git a/chromeos/components/media_app_ui/resources/js/BUILD.gn b/chromeos/components/media_app_ui/resources/js/BUILD.gn
index 80f90ef..4aee0f1c 100644
--- a/chromeos/components/media_app_ui/resources/js/BUILD.gn
+++ b/chromeos/components/media_app_ui/resources/js/BUILD.gn
@@ -49,7 +49,7 @@
   deps = [
     ":message_pipe",
     ":message_types",
-    "//chromeos/components/media_app_ui:mojo_bindings_js_library_for_compile",
+    ":mojo_api_bootstrap",
   ]
 }
 
diff --git a/chromeos/components/media_app_ui/resources/js/launch.js b/chromeos/components/media_app_ui/resources/js/launch.js
index 8f1253c..8f831305 100644
--- a/chromeos/components/media_app_ui/resources/js/launch.js
+++ b/chromeos/components/media_app_ui/resources/js/launch.js
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+/** @typedef {{token: number, file: !File, handle: !FileSystemFileHandle}} */
+let FileDescriptor;
+
 /**
  * Array of entries available in the current directory.
  *
- * @type {Array<{file: !File, handle: !FileSystemFileHandle}>}
+ * @type {!Array<!FileDescriptor>}
  */
 const currentFiles = [];
 
@@ -25,9 +28,9 @@
 
 /**
  * The file currently writable.
- * @type {?FileSystemFileHandle}
+ * @type {?FileDescriptor}
  */
-let currentlyWritableFileHandle = null;
+let currentlyWritableFile = null;
 
 /**
  * Reference to the directory handle that contains the first file in the most
@@ -49,10 +52,10 @@
 
 guestMessagePipe.registerHandler(Message.OVERWRITE_FILE, async (message) => {
   const overwrite = /** @type{OverwriteFileMessage} */ (message);
-  if (!currentlyWritableFileHandle || overwrite.token !== fileToken) {
+  if (!currentlyWritableFile || overwrite.token !== fileToken) {
     throw new Error('File not current.');
   }
-  const writer = await currentlyWritableFileHandle.createWritable();
+  const writer = await currentlyWritableFile.handle.createWritable();
   await writer.write(overwrite.blob);
   await writer.truncate(overwrite.blob.size);
   await writer.close();
@@ -62,7 +65,7 @@
 guestMessagePipe.registerHandler(Message.DELETE_FILE, async (message) => {
   const deleteMsg = /** @type{DeleteFileMessage} */ (message);
 
-  if (!currentlyWritableFileHandle || deleteMsg.token !== fileToken) {
+  if (!currentlyWritableFile || deleteMsg.token !== fileToken) {
     throw new Error('File not current for delete.');
   }
 
@@ -71,7 +74,7 @@
   }
 
   // Get the name from the file reference. Handles file renames.
-  const currentFilename = (await currentlyWritableFileHandle.getFile()).name;
+  const currentFilename = (await currentlyWritableFile.handle.getFile()).name;
 
   // Check the file to be deleted exists in the directory handle. Prevents
   // deleting the wrong file / deleting a file that doesn't exist (this isn't
@@ -80,7 +83,7 @@
   const fileHandle = await currentDirectoryHandle.getFile(currentFilename);
 
   const isSameFileHandle =
-      await fileHandle.isSameEntry(currentlyWritableFileHandle);
+      await fileHandle.isSameEntry(currentlyWritableFile.handle);
   if (!isSameFileHandle) {
     return {deleteResult: DeleteResult.FILE_MOVED};
   }
@@ -89,40 +92,44 @@
   return {deleteResult: DeleteResult.SUCCESS};
 });
 
-/**
- * Loads a file in the guest.
- *
- * @param {?File} file
- * @param {!FileSystemFileHandle} handle
- */
-function loadFile(file, handle) {
-  const token = ++fileToken;
-  currentlyWritableFileHandle = handle;
-  guestMessagePipe.sendMessage(Message.LOAD_FILE, {token, file});
+/** Loads the current file list into the guest. */
+function sendFilesToGuest() {
+  // Before sending to guest ensure writableFileIndex is set to be writable,
+  // also clear the old token.
+  if (currentlyWritableFile) {
+    currentlyWritableFile.token = -1;
+  }
+  currentlyWritableFile = currentFiles[entryIndex];
+  currentlyWritableFile.token = ++fileToken;
+
+  /** @type {!LoadFilesMessage} */
+  const loadFilesMessage = {
+    writableFileIndex: entryIndex,
+    // Handle can't be passed through a message pipe.
+    files: currentFiles.map(fd => ({token: fd.token, file: fd.file}))
+  };
+  guestMessagePipe.sendMessage(Message.LOAD_FILES, loadFilesMessage);
 }
 
 /**
- * Loads a file from a handle received via the fileHandling API.
- *
- * @param {?FileSystemHandle} handle
- * @return {Promise<?File>}
+ * Gets a file from a handle received via the fileHandling API.
+ * @param {?FileSystemHandle} fileSystemHandle
+ * @return {Promise<?{file: !File, handle: !FileSystemFileHandle}>}
  */
-async function loadFileFromHandle(handle) {
-  if (!handle || !handle.isFile) {
+async function getFileFromHandle(fileSystemHandle) {
+  if (!fileSystemHandle || !fileSystemHandle.isFile) {
     return null;
   }
-
-  const fileHandle = /** @type{!FileSystemFileHandle} */ (handle);
-  const file = await fileHandle.getFile();
-  loadFile(file, fileHandle);
-  return file;
+  const handle = /** @type{!FileSystemFileHandle} */ (fileSystemHandle);
+  const file = await handle.getFile();
+  return {file, handle};
 }
 
 /**
  * Changes the working directory and initializes file iteration according to
  * the type of the opened file.
  *
- * @param {FileSystemDirectoryHandle} directory
+ * @param {!FileSystemDirectoryHandle} directory
  * @param {?File} focusFile
  */
 async function setCurrentDirectory(directory, focusFile) {
@@ -131,19 +138,32 @@
   }
   currentFiles.length = 0;
   for await (const /** !FileSystemHandle */ handle of directory.getEntries()) {
-    if (!handle.isFile) {
+    const asFile = await getFileFromHandle(handle);
+    if (!asFile) {
       continue;
     }
-    const fileHandle = /** @type{FileSystemFileHandle} */ (handle);
-    const file = await fileHandle.getFile();
 
     // Only allow traversal of matching mime types.
-    if (file.type === focusFile.type) {
-      currentFiles.push({file, handle: fileHandle});
+    if (asFile.file.type === focusFile.type) {
+      currentFiles.push({token: -1, file: asFile.file, handle: asFile.handle});
     }
   }
   entryIndex = currentFiles.findIndex(i => i.file.name == focusFile.name);
   currentDirectoryHandle = directory;
+  sendFilesToGuest();
+}
+
+/**
+ * Launch the media app with the files in the provided directory.
+ * @param {!FileSystemDirectoryHandle} directory
+ * @param {?FileSystemHandle} initialFileEntry
+ */
+async function launchWithDirectory(directory, initialFileEntry) {
+  const asFile = await getFileFromHandle(initialFileEntry);
+  await setCurrentDirectory(directory, asFile.file);
+
+  // Load currentFiles into the guest.
+  sendFilesToGuest();
 }
 
 /**
@@ -159,8 +179,8 @@
   if (entryIndex < 0) {
     entryIndex += currentFiles.length;
   }
-  const entry = currentFiles[entryIndex];
-  loadFile(entry.file, entry.handle);
+
+  sendFilesToGuest();
 }
 
 document.getElementById('prev-container')
@@ -181,9 +201,8 @@
       console.error('Invalid launch: files[0] is not a directory: ', params);
       return;
     }
-
-    const directory = /** @type{FileSystemDirectoryHandle} */ (params.files[0]);
-    loadFileFromHandle(params.files[1])
-        .then(file => setCurrentDirectory(directory, file));
+    const directory =
+        /** @type{!FileSystemDirectoryHandle} */ (params.files[0]);
+    launchWithDirectory(directory, params.files[1]);
   });
 });
diff --git a/chromeos/components/media_app_ui/resources/js/message_types.js b/chromeos/components/media_app_ui/resources/js/message_types.js
index c1cca24..1cc7e98 100644
--- a/chromeos/components/media_app_ui/resources/js/message_types.js
+++ b/chromeos/components/media_app_ui/resources/js/message_types.js
@@ -13,7 +13,7 @@
  */
 const Message = {
   DELETE_FILE: 'delete-file',
-  LOAD_FILE: 'load-file',
+  LOAD_FILES: 'load-files',
   OPEN_FEEDBACK_DIALOG: 'open-feedback-dialog',
   OVERWRITE_FILE: 'overwrite-file',
 };
@@ -33,8 +33,13 @@
 /** @typedef {{ deleteResult: DeleteResult }}  */
 let DeleteFileResponse;
 
-/** @typedef {{token: number, file: !File}} */
-let OpenFileMessage;
+/**
+ * @typedef {{
+ *    writableFileIndex: number,
+ *    files: !Array<{token: number, file: !File}>
+ * }}
+ */
+let LoadFilesMessage;
 
 /** @typedef {{token: number, blob: !Blob}} */
 let OverwriteFileMessage;
diff --git a/chromeos/components/media_app_ui/resources/js/receiver.js b/chromeos/components/media_app_ui/resources/js/receiver.js
index f17873b3..211a64f2 100644
--- a/chromeos/components/media_app_ui/resources/js/receiver.js
+++ b/chromeos/components/media_app_ui/resources/js/receiver.js
@@ -5,19 +5,6 @@
 /** A pipe through which we can send messages to the parent frame. */
 const parentMessagePipe = new MessagePipe('chrome://media-app', window.parent);
 
-/** @implements mediaApp.AbstractFileList */
-class SingleArrayBufferFileList {
-  /** @param {!mediaApp.AbstractFile} file */
-  constructor(file) {
-    this.file = file;
-    this.length = 1;
-  }
-  /** @override */
-  item(index) {
-    return index === 0 ? this.file : null;
-  }
-}
-
 /**
  * A file received from the privileged context.
  * @implements {mediaApp.AbstractFile}
@@ -72,9 +59,49 @@
   }
 }
 
-parentMessagePipe.registerHandler(Message.LOAD_FILE, (message) => {
-  const fileMessage = /** @type{!OpenFileMessage} */ (message);
-  loadFile(fileMessage.token, fileMessage.file);
+/**
+ * A file list consisting of all files received from the parent. Exposes the
+ * currently writable file and all other readable files in the current
+ * directory.
+ * @implements mediaApp.AbstractFileList
+ */
+class ReceivedFileList {
+  /** @param {!LoadFilesMessage} filesMessage */
+  constructor(filesMessage) {
+    // We make sure the 0th item in the list is the writable one so we
+    // don't break older versions of the media app which uses item(0) instead
+    // of getCurrentlyWritable()
+    // TODO(b/151880563): remove this.
+    let {files, writableFileIndex} = filesMessage;
+    while (writableFileIndex > 0) {
+      files.push(files.shift());
+      writableFileIndex--;
+    }
+
+    this.length = files.length;
+    /** @type {!Array<!ReceivedFile>} */
+    this.files = files.map(f => new ReceivedFile(f.file, f.token));
+    /** @type {number} */
+    this.writableFileIndex = 0;
+  }
+
+  /** @override */
+  item(index) {
+    return this.files[index] || null;
+  }
+
+  /**
+   * Returns the file which is currently writable or null if there isn't one.
+   * @return {?mediaApp.AbstractFile}
+   */
+  getCurrentlyWritable() {
+    return this.item(this.writableFileIndex);
+  }
+}
+
+parentMessagePipe.registerHandler(Message.LOAD_FILES, (message) => {
+  const filesMessage = /** @type{!LoadFilesMessage} */ (message);
+  loadFiles(new ReceivedFileList(filesMessage));
 })
 
 /**
@@ -101,22 +128,16 @@
 }
 
 /**
- * Loads files associated with a message received from the host.
- * @param {number} token
- * @param {!File} file
- * @return {!Promise<!ReceivedFile>} The received file (for testing).
+ * Loads a file list into the media app.
+ * @param {!ReceivedFileList} fileList
  */
-async function loadFile(token, file) {
-  const receivedFile = new ReceivedFile(file, token);
-  const fileList = new SingleArrayBufferFileList(receivedFile);
-
+async function loadFiles(fileList) {
   const app = getApp();
   if (app) {
     await app.loadFiles(fileList);
   } else {
     window.customLaunchData = {files: fileList};
   }
-  return receivedFile;
 }
 
 /**
diff --git a/chromeos/components/media_app_ui/test/driver.js b/chromeos/components/media_app_ui/test/driver.js
index d749b7b..23e0b4eb 100644
--- a/chromeos/components/media_app_ui/test/driver.js
+++ b/chromeos/components/media_app_ui/test/driver.js
@@ -158,3 +158,15 @@
   directory.addFileHandleForTest(new FakeFileSystemFileHandle());
   return directory;
 }
+
+/**
+ * Helper to send a single file to the guest.
+ * @param {!File} file
+ * @param {!FileSystemFileHandle} handle
+ */
+function loadFile(file, handle) {
+  currentFiles.length = 0;
+  currentFiles.push({token: -1, file, handle});
+  entryIndex = 0;
+  sendFilesToGuest();
+}
diff --git a/chromeos/components/media_app_ui/test/driver_api.js b/chromeos/components/media_app_ui/test/driver_api.js
index 6bef3f33..8f1f2960 100644
--- a/chromeos/components/media_app_ui/test/driver_api.js
+++ b/chromeos/components/media_app_ui/test/driver_api.js
@@ -16,6 +16,3 @@
  * }}
  */
 var TestMessageQueryData;
-
-/** @type {MessagePipe} */
-var guestMessagePipe;
diff --git a/chromeos/components/media_app_ui/test/guest_query_receiver.js b/chromeos/components/media_app_ui/test/guest_query_receiver.js
index c8198d19..132668b 100644
--- a/chromeos/components/media_app_ui/test/guest_query_receiver.js
+++ b/chromeos/components/media_app_ui/test/guest_query_receiver.js
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 /**
- * The last file loaded into the guest, updated via a spy on loadFile().
- * @type {?Promise<!ReceivedFile>}
+ * The last file list loaded into the guest, updated via a spy on loadFiles().
+ * @type {?ReceivedFileList}
  */
-let lastReceivedFile = null;
+let lastReceivedFileList = null;
 
 /**
  * Acts on received TestMessageQueryData.
@@ -32,13 +32,12 @@
     }
   } else if (data.overwriteLastFile) {
     const testBlob = new Blob([data.overwriteLastFile]);
-    const ensureLoaded = await lastReceivedFile;
-    await ensureLoaded.overwriteOriginal(testBlob);
+    await lastReceivedFileList.item(0).overwriteOriginal(testBlob);
     result = 'overwriteOriginal resolved';
   } else if (data.deleteLastFile) {
     try {
-      const ensureLoaded = await lastReceivedFile;
-      const deleteResult = await ensureLoaded.deleteOriginalFile();
+      const deleteResult =
+          await lastReceivedFileList.item(0).deleteOriginalFile();
       if (deleteResult === DeleteResult.FILE_MOVED) {
         result = 'deleteOriginalFile resolved file moved';
       } else {
@@ -65,15 +64,15 @@
     throw Error('This is an error');
   });
 
-  // Log errors, rather than send them to console.error.
+  // Log errors, rather than sending them to console.error.
   parentMessagePipe.logClientError = error =>
       console.log(JSON.stringify(error));
 
   // Install spies.
-  const realLoadFile = loadFile;
-  loadFile = async (/** number */ token, /** !File */ file) => {
-    lastReceivedFile = realLoadFile(token, file);
-    return lastReceivedFile;
+  const realLoadFiles = loadFiles;
+  loadFiles = async (/** !ReceivedFileList */ fileList) => {
+    lastReceivedFileList = fileList;
+    realLoadFiles(fileList);
   }
 }
 
diff --git a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
index d7d6c21..e09c559 100644
--- a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
+++ b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
@@ -200,7 +200,7 @@
   // Simulate steps taken to load a file via a launch event.
   const firstFile = directory.files[0];
   loadFile(await createTestImageFile(), firstFile);
-  setCurrentDirectory(directory, firstFile);
+  currentDirectoryHandle = directory;
   let testResponse;
 
   // Nothing should be deleted initially.
@@ -217,7 +217,7 @@
   // File removed from `DirectoryHandle` internal state.
   assertEquals(directory.files.length, 0);
 
-  // Even though the name is the same, the new file shouldn't shouldn't
+  // Even though the name is the same, the new file shouldn't
   // be deleted as it has a different `FileHandle` reference.
   directory.addFileHandleForTest(new FakeFileSystemFileHandle());
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index b24b496..72e7755 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -353,7 +353,7 @@
 
 // Enable or disable resizable floating virtual keyboard on Chrome OS.
 const base::Feature kVirtualKeyboardFloatingResizable{
-    "VirtualKeyboardFloatingResizable", base::FEATURE_ENABLED_BY_DEFAULT};
+    "VirtualKeyboardFloatingResizable", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable or disable MOZC IME to use protobuf as interactive message format.
 const base::Feature kImeMozcProto{"ImeMozcProto",
diff --git a/components/cronet/cronet_global_state_stubs.cc b/components/cronet/cronet_global_state_stubs.cc
index 06171d4..1680ef2d 100644
--- a/components/cronet/cronet_global_state_stubs.cc
+++ b/components/cronet/cronet_global_state_stubs.cc
@@ -69,7 +69,7 @@
     std::unique_ptr<net::ProxyConfigService> proxy_config_service,
     net::NetLog* net_log) {
   return net::ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-      std::move(proxy_config_service), /*quick_check_enabled=*/true, net_log);
+      std::move(proxy_config_service), net_log, /*quick_check_enabled=*/true);
 }
 
 std::string CreateDefaultUserAgent(const std::string& partial_user_agent) {
diff --git a/components/exo/test/exo_test_base_views.cc b/components/exo/test/exo_test_base_views.cc
index 4e8fdf45..87e0911 100644
--- a/components/exo/test/exo_test_base_views.cc
+++ b/components/exo/test/exo_test_base_views.cc
@@ -9,7 +9,6 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ui/base/ime/init/input_method_factory.h"
 #include "ui/display/manager/managed_display_info.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/wm_core_switches.h"
 
 namespace exo {
@@ -105,8 +104,6 @@
 
 void ExoTestBaseViews::SetUp() {
   views::ViewsTestBase::SetUp();
-  // Takes care of its own lifetime.
-  new wm::DefaultActivationClient(root_window());
 
   wm_helper_ = std::make_unique<WMHelperTester>(root_window());
   WMHelper::SetInstance(wm_helper_.get());
diff --git a/components/feed/core/v2/feed_network_impl.cc b/components/feed/core/v2/feed_network_impl.cc
index beaa0b0..5231a842 100644
--- a/components/feed/core/v2/feed_network_impl.cc
+++ b/components/feed/core/v2/feed_network_impl.cc
@@ -12,6 +12,7 @@
 #include "components/feed/core/common/pref_names.h"
 #include "components/feed/core/proto/v2/wire/action_request.pb.h"
 #include "components/feed/core/proto/v2/wire/feed_action_response.pb.h"
+#include "components/feed/core/proto/v2/wire/feed_query.pb.h"
 #include "components/feed/core/proto/v2/wire/request.pb.h"
 #include "components/feed/core/proto/v2/wire/response.pb.h"
 #include "components/prefs/pref_service.h"
@@ -37,6 +38,17 @@
     "https://www.googleapis.com/auth/googlenow";
 constexpr char kApplicationOctetStream[] = "application/octet-stream";
 constexpr base::TimeDelta kNetworkTimeout = base::TimeDelta::FromSeconds(30);
+
+constexpr char kFeedQueryUrl[] =
+    "https://www.google.com/httpservice/retry/InteractiveDiscoverAgaService/"
+    "FeedQuery";
+constexpr char kNextPageQueryUrl[] =
+    "https://www.google.com/httpservice/retry/InteractiveDiscoverAgaService/"
+    "NextPageQuery";
+constexpr char kBackgroundQueryUrl[] =
+    "https://www.google.com/httpservice/noretry/BackgroundDiscoverAgaService/"
+    "FeedQuery";
+
 using RawResponse = FeedNetworkImpl::RawResponse;
 }  // namespace
 
@@ -366,13 +378,27 @@
 void FeedNetworkImpl::SendQueryRequest(
     const feedwire::Request& request,
     base::OnceCallback<void(QueryRequestResult)> callback) {
-  // TODO(harringtond): Decide how we want to override these URLs for testing.
-  // Should probably add a command-line flag.
   std::string binary_proto;
   request.SerializeToString(&binary_proto);
-  GURL url(
-      "https://www.google.com/httpservice/noretry/DiscoverClankService/"
-      "FeedQuery");
+
+  // TODO(harringtond): Decide how we want to override these URLs for testing.
+  // Should probably add a command-line flag.
+  GURL url;
+  switch (request.feed_request().feed_query().reason()) {
+    case feedwire::FeedQuery::SCHEDULED_REFRESH:
+    case feedwire::FeedQuery::IN_PLACE_UPDATE:
+      url = GURL(kBackgroundQueryUrl);
+      break;
+    case feedwire::FeedQuery::NEXT_PAGE_SCROLL:
+      url = GURL(kNextPageQueryUrl);
+      break;
+    case feedwire::FeedQuery::MANUAL_REFRESH:
+      url = GURL(kFeedQueryUrl);
+      break;
+    default:
+      std::move(callback).Run({});
+      return;
+  }
 
   AddMothershipPayloadQueryParams(/*is_post=*/false, binary_proto,
                                   delegate_->GetLanguageTag(), &url);
diff --git a/components/feed/core/v2/feed_network_impl_unittest.cc b/components/feed/core/v2/feed_network_impl_unittest.cc
index e3003d1..2a0ea1d 100644
--- a/components/feed/core/v2/feed_network_impl_unittest.cc
+++ b/components/feed/core/v2/feed_network_impl_unittest.cc
@@ -40,9 +40,11 @@
 using ActionRequestResult = FeedNetwork::ActionRequestResult;
 using QueryRequestResult = FeedNetwork::QueryRequestResult;
 
-feedwire::Request GetTestFeedRequest() {
+feedwire::Request GetTestFeedRequest(feedwire::FeedQuery::RequestReason reason =
+                                         feedwire::FeedQuery::MANUAL_REFRESH) {
   feedwire::Request request;
   request.set_request_version(feedwire::Request::FEED_QUERY);
+  request.mutable_feed_request()->mutable_feed_query()->set_reason(reason);
   return request;
 }
 
@@ -182,12 +184,11 @@
 TEST_F(FeedNetworkTest, SendQueryRequestEmpty) {
   CallbackReceiver<QueryRequestResult> receiver;
   feed_network()->SendQueryRequest(feedwire::Request(), receiver.Bind());
-  RespondToQueryRequest("", net::HTTP_OK);
 
   ASSERT_TRUE(receiver.GetResult());
   const QueryRequestResult& result = *receiver.GetResult();
-  EXPECT_EQ(net::HTTP_OK, result.status_code);
-  EXPECT_TRUE(result.response_body);
+  EXPECT_EQ(0, result.status_code);
+  EXPECT_FALSE(result.response_body);
 }
 
 TEST_F(FeedNetworkTest, SendQueryRequestSendsValidRequest) {
@@ -197,8 +198,8 @@
       RespondToQueryRequest("", net::HTTP_OK);
 
   EXPECT_EQ(
-      "https://www.google.com/httpservice/noretry/"
-      "DiscoverClankService/FeedQuery?reqpld=%08%01&fmt=bin&hl=en",
+      "https://www.google.com/httpservice/retry/InteractiveDiscoverAgaService/"
+      "FeedQuery?reqpld=%08%01%C2%3E%04%12%02%08%01&fmt=bin&hl=en",
       resource_request.url);
   EXPECT_EQ("GET", resource_request.method);
   EXPECT_FALSE(resource_request.headers.HasHeader("content-encoding"));
@@ -221,7 +222,7 @@
 
 TEST_F(FeedNetworkTest, SendQueryRequestReceivesResponse) {
   CallbackReceiver<QueryRequestResult> receiver;
-  feed_network()->SendQueryRequest(feedwire::Request(), receiver.Bind());
+  feed_network()->SendQueryRequest(GetTestFeedRequest(), receiver.Bind());
   RespondToQueryRequest(GetTestFeedResponse(), net::HTTP_OK);
 
   ASSERT_TRUE(receiver.GetResult());
@@ -233,7 +234,7 @@
 
 TEST_F(FeedNetworkTest, SendQueryRequestIgnoresBodyForNon200Response) {
   CallbackReceiver<QueryRequestResult> receiver;
-  feed_network()->SendQueryRequest(feedwire::Request(), receiver.Bind());
+  feed_network()->SendQueryRequest(GetTestFeedRequest(), receiver.Bind());
   RespondToQueryRequest(GetTestFeedResponse(), net::HTTP_FORBIDDEN);
 
   ASSERT_TRUE(receiver.GetResult());
@@ -254,7 +255,7 @@
 TEST_F(FeedNetworkTest, RequestTimeout) {
   base::HistogramTester histogram_tester;
   CallbackReceiver<QueryRequestResult> receiver;
-  feed_network()->SendQueryRequest(feedwire::Request(), receiver.Bind());
+  feed_network()->SendQueryRequest(GetTestFeedRequest(), receiver.Bind());
   task_environment_.FastForwardBy(TimeDelta::FromSeconds(30));
 
   ASSERT_TRUE(receiver.GetResult());
@@ -268,7 +269,11 @@
 TEST_F(FeedNetworkTest, ParallelRequests) {
   CallbackReceiver<QueryRequestResult> receiver1, receiver2;
   feed_network()->SendQueryRequest(GetTestFeedRequest(), receiver1.Bind());
-  feed_network()->SendQueryRequest(feedwire::Request(), receiver2.Bind());
+  // Make another request with a different URL so Respond() won't affect both
+  // requests.
+  feed_network()->SendQueryRequest(
+      GetTestFeedRequest(feedwire::FeedQuery::NEXT_PAGE_SCROLL),
+      receiver2.Bind());
 
   // Respond to both requests, avoiding FastForwardUntilNoTasksRemain until
   // a response is added for both requests.
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.cc b/components/leveldb_proto/public/shared_proto_database_client_list.cc
index 61bdad0..bb4eda1 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.cc
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.cc
@@ -79,6 +79,8 @@
       return "PrintJobDatabase";
     case ProtoDbType::FEED_STREAM_DATABASE:
       return "FeedStreamDatabase";
+    case ProtoDbType::TAB_STATE_DATABASE:
+      return "TabStateDatabase";
     case ProtoDbType::LAST:
       NOTREACHED();
       return std::string();
diff --git a/components/leveldb_proto/public/shared_proto_database_client_list.h b/components/leveldb_proto/public/shared_proto_database_client_list.h
index 5039ff1..7fbd8f20 100644
--- a/components/leveldb_proto/public/shared_proto_database_client_list.h
+++ b/components/leveldb_proto/public/shared_proto_database_client_list.h
@@ -49,6 +49,7 @@
   // DB Used by shared database, will always be unique.
   SHARED_DB_METADATA = 25,
   FEED_STREAM_DATABASE = 26,
+  TAB_STATE_DATABASE = 27,
   LAST,
 };
 
@@ -60,6 +61,7 @@
     ProtoDbType::NOTIFICATION_SCHEDULER_NOTIFICATION_STORE,
     ProtoDbType::PRINT_JOB_DATABASE,
     ProtoDbType::FEED_STREAM_DATABASE,
+    ProtoDbType::TAB_STATE_DATABASE,
     ProtoDbType::LAST,  // Marks the end of list.
 };
 
diff --git a/components/omnibox/browser/location_bar_model_impl.cc b/components/omnibox/browser/location_bar_model_impl.cc
index 09df5411..ee45814 100644
--- a/components/omnibox/browser/location_bar_model_impl.cc
+++ b/components/omnibox/browser/location_bar_model_impl.cc
@@ -55,9 +55,12 @@
     format_types |= url_formatter::kFormatUrlTrimAfterHost;
   }
 
-  // Early exit to prevent elision of URLs when relevant extension is enabled.
+  // Early exit to prevent elision of URLs when relevant extension or pref is
+  // enabled.
   if (delegate_->ShouldPreventElision()) {
-    return GetFormattedURL(format_types);
+    url_formatter::FormatUrlTypes full_url_format_types = format_types &=
+        ~url_formatter::kFormatUrlOmitHTTP;
+    return GetFormattedURL(full_url_format_types);
   }
 
 #if defined(OS_IOS)
diff --git a/components/paint_preview/renderer/paint_preview_recorder_utils.cc b/components/paint_preview/renderer/paint_preview_recorder_utils.cc
index 55ced52..de44d6b 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_utils.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_utils.cc
@@ -23,8 +23,7 @@
       // Recurse into nested records if they contain text blobs (equivalent to
       // nested SkPictures).
       auto* record_op = static_cast<cc::DrawRecordOp*>(*it);
-      if (record_op->HasText())
-        ParseGlyphs(record_op->record.get(), tracker);
+      ParseGlyphs(record_op->record.get(), tracker);
     }
   }
 }
diff --git a/components/proxy_config/ios/proxy_service_factory.cc b/components/proxy_config/ios/proxy_service_factory.cc
index 4fd69797..f24037e5 100644
--- a/components/proxy_config/ios/proxy_service_factory.cc
+++ b/components/proxy_config/ios/proxy_service_factory.cc
@@ -54,6 +54,6 @@
   DCHECK_CURRENTLY_ON(web::WebThread::IO);
   std::unique_ptr<net::ProxyResolutionService> proxy_resolution_service(
       net::ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-          std::move(proxy_config_service), quick_check_enabled, net_log));
+          std::move(proxy_config_service), net_log, quick_check_enabled));
   return proxy_resolution_service;
 }
diff --git a/content/browser/bad_message.cc b/content/browser/bad_message.cc
index 3c6dd492..da9410d 100644
--- a/content/browser/bad_message.cc
+++ b/content/browser/bad_message.cc
@@ -70,12 +70,6 @@
   filter->ShutdownForBadMessage();
 }
 
-base::debug::CrashKeyString* GetMojoErrorCrashKey() {
-  static auto* crash_key = base::debug::AllocateCrashKeyString(
-      "mojo-message-error", base::debug::CrashKeySize::Size256);
-  return crash_key;
-}
-
 base::debug::CrashKeyString* GetRequestedSiteURLKey() {
   static auto* crash_key = base::debug::AllocateCrashKeyString(
       "requested_site_url", base::debug::CrashKeySize::Size64);
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 30109a0..473a6a9 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -275,10 +275,6 @@
 // for the |reason|, and terminates the process for |filter|.
 void ReceivedBadMessage(BrowserMessageFilter* filter, BadMessageReason reason);
 
-// Returns a crash key named "mojo-message-error" for storing Mojo error
-// messages.
-base::debug::CrashKeyString* GetMojoErrorCrashKey();
-
 // Site isolation. These keys help debug renderer kills such as
 // https://crbug.com/773140.
 // Retuns a key named "requested_site_url".
diff --git a/content/browser/browser_child_process_host_impl.cc b/content/browser/browser_child_process_host_impl.cc
index a4b37d8..207427f 100644
--- a/content/browser/browser_child_process_host_impl.cc
+++ b/content/browser/browser_child_process_host_impl.cc
@@ -7,7 +7,6 @@
 #include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
@@ -30,7 +29,6 @@
 #include "build/build_config.h"
 #include "components/tracing/common/trace_startup_config.h"
 #include "components/tracing/common/tracing_switches.h"
-#include "content/browser/bad_message.h"
 #include "content/browser/browser_main_loop.h"
 #include "content/browser/histogram_controller.h"
 #include "content/browser/tracing/background_tracing_manager_impl.h"
@@ -50,6 +48,7 @@
 #include "content/public/common/process_type.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/sandboxed_process_launcher_delegate.h"
+#include "mojo/public/cpp/bindings/scoped_message_error_crash_key.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "net/websockets/websocket_basic_stream.h"
 #include "net/websockets/websocket_channel.h"
@@ -694,8 +693,7 @@
   // It is important to call DumpWithoutCrashing synchronously - this will help
   // to preserve the callstack and the crash keys present when the bad mojo
   // message was received.
-  base::debug::ScopedCrashKeyString scoped_error_key(
-      bad_message::GetMojoErrorCrashKey(), error);
+  mojo::debug::ScopedMessageErrorCrashKey scoped_error_key(error);
   base::debug::DumpWithoutCrashing();
 
   if (task_runner->BelongsToCurrentThread()) {
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 5a1007cd..faa7ecb 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -202,6 +202,7 @@
 #include "media/webrtc/webrtc_switches.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/scoped_message_error_crash_key.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "services/device/public/mojom/battery_monitor.mojom.h"
@@ -4980,8 +4981,7 @@
 
   // The ReceivedBadMessage call below will trigger a DumpWithoutCrashing.
   // Capture the error message in a crash key value.
-  base::debug::ScopedCrashKeyString error_key_value(
-      bad_message::GetMojoErrorCrashKey(), error);
+  mojo::debug::ScopedMessageErrorCrashKey error_key_value(error);
   bad_message::ReceivedBadMessage(render_process_id,
                                   bad_message::RPH_MOJO_PROCESS_ERROR);
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index 725b698a..ae198a21 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -116,7 +116,6 @@
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/selection_bound.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/default_screen_position_client.h"
 #include "ui/wm/core/window_util.h"
 
@@ -498,7 +497,6 @@
     aura_test_helper_.reset(new aura::test::AuraTestHelper());
     aura_test_helper_->SetUp(
         ImageTransportFactory::GetInstance()->GetContextFactory());
-    new wm::DefaultActivationClient(aura_test_helper_->GetContext());
 
     browser_context_.reset(new TestBrowserContext);
     process_host_ = new MockRenderProcessHost(browser_context_.get());
diff --git a/content/browser/service_worker/service_worker_cache_writer_unittest.cc b/content/browser/service_worker/service_worker_cache_writer_unittest.cc
index 665db01..a0a5fa8c 100644
--- a/content/browser/service_worker/service_worker_cache_writer_unittest.cc
+++ b/content/browser/service_worker/service_worker_cache_writer_unittest.cc
@@ -146,8 +146,11 @@
   }
 
   net::Error WriteHeaders(size_t len) {
-    scoped_refptr<HttpResponseInfoIOBuffer> buf(new HttpResponseInfoIOBuffer);
+    auto buf = base::MakeRefCounted<HttpResponseInfoIOBuffer>();
     buf->response_data_size = len;
+    buf->http_info = std::make_unique<net::HttpResponseInfo>();
+    buf->http_info->headers =
+        base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.0 200 OK\0\0");
     return cache_writer_->MaybeWriteHeaders(buf.get(), CreateWriteCallback());
   }
 
diff --git a/content/browser/service_worker/service_worker_register_job.cc b/content/browser/service_worker/service_worker_register_job.cc
index 7a4baf4..26c0b7d 100644
--- a/content/browser/service_worker/service_worker_register_job.cc
+++ b/content/browser/service_worker/service_worker_register_job.cc
@@ -659,6 +659,9 @@
     // TODO(falken): Move this further down. The spec says to set status to
     // 'redundant' after promoting the new version to .waiting attribute and
     // 'installed' status.
+    // TODO(crbug.com/951571): Remove this once we identified the cause of
+    // crash.
+    CHECK(!registration()->waiting_version()->HasControllee());
     registration()->waiting_version()->SetStatus(
         ServiceWorkerVersion::REDUNDANT);
   }
diff --git a/content/browser/service_worker/service_worker_registration.cc b/content/browser/service_worker/service_worker_registration.cc
index d32c939..54fec83 100644
--- a/content/browser/service_worker/service_worker_registration.cc
+++ b/content/browser/service_worker/service_worker_registration.cc
@@ -505,6 +505,11 @@
       observer.OnSkippedWaiting(this);
   }
 
+  // TODO(crbug.com/951571): Remove this once we identified the cause of crash.
+  if (exiting_version) {
+    CHECK(!exiting_version->HasControllee());
+  }
+
   // "10. Queue a task to fire an event named activate..."
   // The browser could be shutting down. To avoid spurious start worker
   // failures, wait a bit before continuing.
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index 7e4de17..8c7a60a6 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -376,6 +376,9 @@
   } else if (status == REDUNDANT) {
     embedded_worker_->OnWorkerVersionDoomed();
 
+    // TODO(crbug.com/1021718): Remove this check after we identified the cause.
+    CHECK(bfcached_controllee_map_.empty());
+
     // TODO(crbug.com/951571): Remove this once we figured out the cause of
     // invalid controller status.
     redundant_state_callstack_ = base::debug::StackTrace();
@@ -763,6 +766,10 @@
 
   // TODO(yuzus, crbug.com/951571): Remove these CHECKs once we figure out the
   // cause of crash.
+  if (status_ == REDUNDANT) {
+    DEBUG_ALIAS_FOR_CSTR(redundant_callstack_str,
+                         redundant_state_callstack_.ToString().c_str(), 1024);
+  }
   CHECK_NE(status_, NEW);
   CHECK_NE(status_, INSTALLING);
   CHECK_NE(status_, INSTALLED);
@@ -951,7 +958,9 @@
     container_host->NotifyControllerLost();
   }
   // Any controllee this version had should have removed itself.
-  DCHECK(!HasControllee());
+  // TODO(crbug.com/951571): Change to DCHECK once we identified the cause of
+  // crash.
+  CHECK(!HasControllee());
 
   SetStatus(REDUNDANT);
   if (running_status() == EmbeddedWorkerStatus::STARTING ||
diff --git a/content/public/test/test_renderer_host.cc b/content/public/test/test_renderer_host.cc
index f32c4e3..95108f8 100644
--- a/content/public/test/test_renderer_host.cc
+++ b/content/public/test/test_renderer_host.cc
@@ -49,7 +49,6 @@
 
 #if defined(USE_AURA)
 #include "ui/aura/test/aura_test_helper.h"
-#include "ui/wm/core/default_activation_client.h"
 #endif
 
 #if defined(OS_MACOSX)
@@ -246,7 +245,6 @@
 
   aura_test_helper_.reset(new aura::test::AuraTestHelper());
   aura_test_helper_->SetUp(context_factory);
-  new wm::DefaultActivationClient(aura_test_helper_->GetContext());
 #endif
 
   sanity_checker_.reset(new ContentBrowserSanityChecker());
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.cc b/content/renderer/pepper/pepper_graphics_2d_host.cc
index 57e2ca6..354a6e5d 100644
--- a/content/renderer/pepper/pepper_graphics_2d_host.cc
+++ b/content/renderer/pepper/pepper_graphics_2d_host.cc
@@ -31,7 +31,7 @@
 #include "content/renderer/pepper/ppb_image_data_impl.h"
 #include "content/renderer/render_thread_impl.h"
 #include "gpu/GLES2/gl2extchromium.h"
-#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/client/raster_interface.h"
 #include "gpu/command_buffer/common/capabilities.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
@@ -585,7 +585,7 @@
 // static
 void PepperGraphics2DHost::ReleaseTextureCallback(
     base::WeakPtr<PepperGraphics2DHost> host,
-    scoped_refptr<viz::ContextProvider> context,
+    scoped_refptr<viz::RasterContextProvider> context,
     const gfx::Size& size,
     const gpu::Mailbox& mailbox,
     const gpu::SyncToken& sync_token,
@@ -608,7 +608,7 @@
   // reuse the shared images, they are invalid. If the compositing mode changed,
   // the context will be lost also, so we get both together.
   if (!main_thread_context_ ||
-      main_thread_context_->ContextGL()->GetGraphicsResetStatusKHR() !=
+      main_thread_context_->RasterInterface()->GetGraphicsResetStatusKHR() !=
           GL_NO_ERROR) {
     recycled_shared_images_.clear();
     main_thread_context_ = nullptr;
@@ -639,7 +639,7 @@
   // When gpu compositing, the compositor expects gpu resources, so we copy the
   // |image_data_| into a texture.
   if (main_thread_context_) {
-    auto* gl = main_thread_context_->ContextGL();
+    auto* ri = main_thread_context_->RasterInterface();
     auto* sii = main_thread_context_->SharedImageInterface();
 
     // The bitmap in |image_data_| uses the skia N32 byte order.
@@ -700,19 +700,15 @@
       src = swizzled.get();
     }
 
-    gl->WaitSyncTokenCHROMIUM(in_sync_token.GetConstData());
-    GLuint texture_id =
-        gl->CreateAndTexStorage2DSharedImageCHROMIUM(gpu_mailbox.name);
-    gl->BeginSharedImageAccessDirectCHROMIUM(
-        texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
-    gl->BindTexture(texture_target, texture_id);
-    gl->TexSubImage2D(texture_target, 0, 0, 0, size.width(), size.height(),
-                      viz::GLDataFormat(format), viz::GLDataType(format), src);
-    gl->BindTexture(texture_target, 0);
-    gl->EndSharedImageAccessDirectCHROMIUM(texture_id);
-    gl->DeleteTextures(1, &texture_id);
+    SkImageInfo src_info =
+        SkImageInfo::Make(size.width(), size.height(),
+                          viz::ResourceFormatToClosestSkColorType(true, format),
+                          kUnknown_SkAlphaType);
+    ri->WaitSyncTokenCHROMIUM(in_sync_token.GetConstData());
+    ri->WritePixels(gpu_mailbox, 0, 0, texture_target, src_info, src);
+
     gpu::SyncToken out_sync_token;
-    gl->GenUnverifiedSyncTokenCHROMIUM(out_sync_token.GetData());
+    ri->GenUnverifiedSyncTokenCHROMIUM(out_sync_token.GetData());
 
     image_data_->Unmap();
     swizzled.reset();
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.h b/content/renderer/pepper/pepper_graphics_2d_host.h
index b68e0f4..e0f74b68 100644
--- a/content/renderer/pepper/pepper_graphics_2d_host.h
+++ b/content/renderer/pepper/pepper_graphics_2d_host.h
@@ -33,7 +33,7 @@
 }
 
 namespace viz {
-class ContextProvider;
+class RasterContextProvider;
 class SingleReleaseCallback;
 struct TransferableResource;
 }
@@ -185,7 +185,7 @@
   // has been destroyed.
   static void ReleaseTextureCallback(
       base::WeakPtr<PepperGraphics2DHost> host,
-      scoped_refptr<viz::ContextProvider> context,
+      scoped_refptr<viz::RasterContextProvider> context,
       const gfx::Size& size,
       const gpu::Mailbox& mailbox,
       const gpu::SyncToken& sync_token,
@@ -234,7 +234,7 @@
   bool is_gpu_compositing_disabled_ = false;
   // The shared main thread context provider, used to upload 2d pepper frames
   // if the compositor is expecting gpu content.
-  scoped_refptr<viz::ContextProvider> main_thread_context_;
+  scoped_refptr<viz::RasterContextProvider> main_thread_context_;
   struct SharedImageInfo {
     SharedImageInfo(gpu::SyncToken sync_token,
                     gpu::Mailbox mailbox,
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index d5d26d63..3265990 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -45,7 +45,7 @@
   public_deps = [ "//content/public/common:content_descriptors" ]
 }
 
-source_set("web_test_switches") {
+source_set("web_test_common") {
   testonly = true
   sources = [
     "common/web_test/web_test_string_util.cc",
@@ -241,7 +241,7 @@
   deps = [
     ":client_hints_util",
     ":resources",
-    ":web_test_switches",
+    ":web_test_common",
     "//base",
     "//base:base_static",
     "//base/third_party/dynamic_annotations",
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 06e23ef..d4235d0e 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -40,8 +40,6 @@
 #include "content/shell/browser/shell_devtools_manager_delegate.h"
 #include "content/shell/browser/shell_quota_permission_context.h"
 #include "content/shell/browser/shell_web_contents_view_delegate_creator.h"
-#include "content/shell/common/blink_test.mojom.h"
-#include "content/shell/common/power_monitor_test.mojom.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/common/web_test/fake_bluetooth_chooser.mojom.h"
 #include "content/shell/common/web_test/web_test_bluetooth_fake_adapter_setter.mojom.h"
diff --git a/content/shell/test_runner/BUILD.gn b/content/shell/test_runner/BUILD.gn
index 1d93c8319..1f4a891 100644
--- a/content/shell/test_runner/BUILD.gn
+++ b/content/shell/test_runner/BUILD.gn
@@ -95,7 +95,7 @@
     "//content/public/renderer",
     "//content/renderer:for_content_tests",
     "//content/shell:client_hints_util",
-    "//content/shell:web_test_switches",
+    "//content/shell:web_test_common",
     "//device/base/synchronization",
     "//device/gamepad/public/cpp:shared_with_blink",
     "//device/gamepad/public/mojom",
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index 48f88dd3..acd850c 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -22,7 +22,6 @@
 #include "build/build_config.h"
 #include "cc/paint/paint_canvas.h"
 #include "content/shell/common/web_test/web_test_string_util.h"
-#include "content/shell/common/web_test/web_test_switches.h"
 #include "content/shell/test_runner/layout_dump.h"
 #include "content/shell/test_runner/mock_content_settings_client.h"
 #include "content/shell/test_runner/mock_web_document_subresource_filter.h"
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index fd14540..617182f 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -755,7 +755,6 @@
     "//content/public/common",
     "//content/public/renderer",
     "//content/renderer:for_content_tests",
-    "//content/shell:web_test_switches",
     "//content/shell/test_runner:test_runner",
     "//device/bluetooth",
     "//device/gamepad/public/cpp:shared_with_blink",
diff --git a/content/test/data/gpu/pixel_video_context_loss.html b/content/test/data/gpu/pixel_video_context_loss.html
index 92e2b0f5..be85115 100644
--- a/content/test/data/gpu/pixel_video_context_loss.html
+++ b/content/test/data/gpu/pixel_video_context_loss.html
@@ -1,11 +1,4 @@
 <!DOCTYPE HTML>
-
-<!-- READ BEFORE UPDATING:
-If this test is updated make sure to increment the "revision" value of the
-associated test in content/test/gpu/page_sets/pixel_tests.py. This will ensure
-that the baseline images are regenerated on the next run.
--->
-
 <html>
 <head>
 <meta name="viewport" content="initial-scale=1">
@@ -35,6 +28,9 @@
 }();
 
 function CrashGpuProcess() {
+  // Ensure we only get one crash in case of multiple canplaythrough events.
+  video.removeEventListener('canplaythrough', CrashGpuProcess, false);
+
   // Create a canvas element and webgl context -- without a context, we won't
   // get a webglcontextlost event.
   var canvas = document.createElement('canvas');
diff --git a/content/test/web_test_support.cc b/content/test/web_test_support.cc
index 1f0be5b..d079abf 100644
--- a/content/test/web_test_support.cc
+++ b/content/test/web_test_support.cc
@@ -23,7 +23,6 @@
 #include "content/renderer/render_view_impl.h"
 #include "content/renderer/render_widget.h"
 #include "content/shell/common/shell_switches.h"
-#include "content/shell/common/web_test/web_test_switches.h"
 #include "content/shell/renderer/web_test/blink_test_runner.h"
 #include "content/shell/renderer/web_test/web_test_render_thread_observer.h"
 #include "content/shell/test_runner/web_frame_test_proxy.h"
diff --git a/docs/threading_and_tasks_testing.md b/docs/threading_and_tasks_testing.md
index 1ab22e3..5110be1 100644
--- a/docs/threading_and_tasks_testing.md
+++ b/docs/threading_and_tasks_testing.md
@@ -54,7 +54,7 @@
  public:
   Foo() : owning_sequence_(base::SequencedTaskRunnerHandle::Get()) {}
 
-  DoSomethingAndReply(base::OnceClosure reply) {
+  DoSomethingAndReply(base::OnceClosure on_done) {
     DCHECK(owning_sequence_->RunsTasksInCurrentSequence());
     something_was_done_ = true;
     owning_sequence_->PostTask(on_done);
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index 575ad0068..33f4dd83 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -388,6 +388,7 @@
     "//crypto:platform",
     "//crypto:platform",
     "//extensions:extensions_browser_resources",
+    "//extensions/browser/api/declarative_net_request:core",
     "//extensions/browser/guest_view/web_view/web_ui",
     "//extensions/buildflags",
     "//extensions/common",
diff --git a/extensions/browser/api/declarative_net_request/BUILD.gn b/extensions/browser/api/declarative_net_request/BUILD.gn
index 5792450..cfdbece3 100644
--- a/extensions/browser/api/declarative_net_request/BUILD.gn
+++ b/extensions/browser/api/declarative_net_request/BUILD.gn
@@ -8,20 +8,12 @@
     "action_tracker.h",
     "composite_matcher.cc",
     "composite_matcher.h",
-    "constants.cc",
-    "constants.h",
     "declarative_net_request_api.cc",
     "declarative_net_request_api.h",
     "extension_url_pattern_index_matcher.cc",
     "extension_url_pattern_index_matcher.h",
     "file_sequence_helper.cc",
     "file_sequence_helper.h",
-    "flat_ruleset_indexer.cc",
-    "flat_ruleset_indexer.h",
-    "indexed_rule.cc",
-    "indexed_rule.h",
-    "parse_info.cc",
-    "parse_info.h",
     "regex_rules_matcher.cc",
     "regex_rules_matcher.h",
     "request_action.cc",
@@ -36,10 +28,6 @@
     "ruleset_matcher.h",
     "ruleset_matcher_base.cc",
     "ruleset_matcher_base.h",
-    "ruleset_source.cc",
-    "ruleset_source.h",
-    "utils.cc",
-    "utils.h",
     "web_contents_helper.cc",
     "web_contents_helper.h",
   ]
@@ -47,6 +35,7 @@
   public_deps = [
     "//components/url_matcher",
     "//components/url_pattern_index",
+    "//extensions/browser/api/declarative_net_request:core",
     "//extensions/browser/api/declarative_net_request/flat:extension_ruleset",
     "//third_party/re2",
   ]
@@ -59,6 +48,48 @@
     "//extensions/common",
     "//extensions/common/api",
     "//net",
+    "//services/data_decoder/public/cpp:cpp",
+    "//tools/json_schema_compiler:generated_api_util",
+    "//url",
+  ]
+}
+
+# This is the set of sources required by the core extension system (i.e. stuff
+# in //extensions/browser:browser_sources). This is split into another
+# source set to prevent a cyclic dependency. This is necessary since
+# declarativeNetRequest API needs to participate in the installation flow.
+# TODO(crbug.com/730220): Remove this source set once extensions/browser is
+# changed to be a monolithic target or once the core extension system doesn't
+# rely on these.
+source_set("core") {
+  sources = [
+    "constants.cc",
+    "constants.h",
+    "flat_ruleset_indexer.cc",
+    "flat_ruleset_indexer.h",
+    "indexed_rule.cc",
+    "indexed_rule.h",
+    "parse_info.cc",
+    "parse_info.h",
+    "ruleset_checksum.h",
+    "ruleset_source.cc",
+    "ruleset_source.h",
+    "utils.cc",
+    "utils.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/url_pattern_index",
+    "//components/web_cache/browser:browser",
+    "//content/public/browser:browser",
+    "//extensions/browser/api/declarative_net_request/flat:extension_ruleset",
+    "//extensions/common",
+    "//extensions/common/api",
+    "//net",
+    "//services/data_decoder/public/cpp:cpp",
+    "//third_party/flatbuffers:flatbuffers",
+    "//third_party/re2",
     "//tools/json_schema_compiler:generated_api_util",
     "//url",
   ]
diff --git a/extensions/common/file_util.cc b/extensions/common/file_util.cc
index ec03de3f..58a83b9 100644
--- a/extensions/common/file_util.cc
+++ b/extensions/common/file_util.cc
@@ -466,15 +466,17 @@
 
 bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
                               const Extension* extension,
-                              int error_message_id,
+                              const char* manifest_key,
                               SkColor background_color,
                               std::string* error) {
   for (const auto& entry : icon_set.map()) {
     const base::FilePath path =
         extension->GetResource(entry.second).GetFilePath();
     if (!ValidateFilePath(path)) {
-      *error = l10n_util::GetStringFUTF8(error_message_id,
-                                         base::UTF8ToUTF16(entry.second));
+      constexpr char kIconMissingError[] =
+          "Could not load icon '%s' specified in '%s'.";
+      *error = base::StringPrintf(kIconMissingError, entry.second.c_str(),
+                                  manifest_key);
       return false;
     }
 
@@ -491,9 +493,10 @@
           "Extensions.ManifestIconSetIconWasVisibleForUnpackedRendered",
           is_sufficiently_visible_rendered);
       if (!is_sufficiently_visible && g_report_error_for_invisible_icon) {
-        *error = l10n_util::GetStringFUTF8(
-            IDS_EXTENSION_LOAD_ICON_NOT_SUFFICIENTLY_VISIBLE,
-            base::UTF8ToUTF16(entry.second));
+        constexpr char kIconNotSufficientlyVisibleError[] =
+            "Icon '%s' specified in '%s' is not sufficiently visible.";
+        *error = base::StringPrintf(kIconNotSufficientlyVisibleError,
+                                    entry.second.c_str(), manifest_key);
         return false;
       }
     }
diff --git a/extensions/common/file_util.h b/extensions/common/file_util.h
index 41c64ab1..a765714 100644
--- a/extensions/common/file_util.h
+++ b/extensions/common/file_util.h
@@ -120,14 +120,12 @@
 // an error.
 void SetReportErrorForInvisibleIconForTesting(bool value);
 
-// Returns true if the icons in |icon_set| exist. Otherwise, populates
-// |error| with the |error_message_id| for an invalid file. If an icon
-// is not sufficiently visible, and error checking is enabled, |error|
-// is populated with a different message, rather than one specified
-// by |error_message_id|.
+// Returns true if the icons in |icon_set| exist, and, if enabled, checks that
+// they are sufficiently visible compared to |background_color|. On failure,
+// populates |error|, which will include the given |manifest_key|.
 bool ValidateExtensionIconSet(const ExtensionIconSet& icon_set,
                               const Extension* extension,
-                              int error_message_id,
+                              const char* manifest_key,
                               SkColor background_color,
                               std::string* error);
 
diff --git a/extensions/common/file_util_unittest.cc b/extensions/common/file_util_unittest.cc
index f1cfdebd..171bf902 100644
--- a/extensions/common/file_util_unittest.cc
+++ b/extensions/common/file_util_unittest.cc
@@ -536,7 +536,8 @@
   scoped_refptr<Extension> extension(file_util::LoadExtension(
       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   EXPECT_FALSE(extension);
-  EXPECT_EQ("Could not load extension icon 'missing-icon.png'.", error);
+  EXPECT_EQ("Could not load icon 'missing-icon.png' specified in 'icons'.",
+            error);
 }
 
 // Try to install an unpacked extension with an invisible icon. This
@@ -555,8 +556,10 @@
       ext_dir, Manifest::UNPACKED, Extension::NO_FLAGS, &error));
   file_util::SetReportErrorForInvisibleIconForTesting(false);
   EXPECT_FALSE(extension);
-  EXPECT_EQ("The icon is not sufficiently visible 'invisible_icon.png'.",
-            error);
+  EXPECT_EQ(
+      "Icon 'invisible_icon.png' specified in 'icons' is not "
+      "sufficiently visible.",
+      error);
 }
 
 // Try to install a packed extension with an invisible icon. This should
diff --git a/extensions/common/manifest_handlers/icons_handler.cc b/extensions/common/manifest_handlers/icons_handler.cc
index f99a7a1..a299e13 100644
--- a/extensions/common/manifest_handlers/icons_handler.cc
+++ b/extensions/common/manifest_handlers/icons_handler.cc
@@ -81,7 +81,7 @@
   // Analyze the icons for visibility using the default toolbar color, since
   // the majority of Chrome users don't modify their theme.
   return file_util::ValidateExtensionIconSet(
-      IconsInfo::GetIcons(extension), extension, IDS_EXTENSION_LOAD_ICON_FAILED,
+      IconsInfo::GetIcons(extension), extension, manifest_keys::kIcons,
       image_util::kDefaultToolbarColor, error);
 }
 
diff --git a/extensions/strings/extensions_strings.grd b/extensions/strings/extensions_strings.grd
index 9540da4..da4c0d3 100644
--- a/extensions/strings/extensions_strings.grd
+++ b/extensions/strings/extensions_strings.grd
@@ -202,12 +202,6 @@
       <message name="IDS_EXTENSION_LOAD_CSS_FAILED" desc="">
         Could not load css '<ph name="RELATIVE_PATH">$1<ex>file.css</ex></ph>' for content script.
       </message>
-      <message name="IDS_EXTENSION_LOAD_ICON_FAILED" desc="">
-        Could not load extension icon '<ph name="ICON">$1<ex>icon.png</ex></ph>'.
-      </message>
-      <message name="IDS_EXTENSION_LOAD_ICON_NOT_SUFFICIENTLY_VISIBLE" desc="">
-        The icon is not sufficiently visible '<ph name="ICON">$1<ex>icon.png</ex></ph>'.
-      </message>
       <message name="IDS_EXTENSION_LOAD_JAVASCRIPT_FAILED" desc="">
         Could not load javascript '<ph name="RELATIVE_PATH">$1<ex>javas.js</ex></ph>' for content script.
       </message>
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index 8096533..5279574c7 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1072,6 +1072,15 @@
   CheckGLError();
 }
 
+void RasterImplementation::WritePixels(const gpu::Mailbox& dest_mailbox,
+                                       int dst_x_offset,
+                                       int dst_y_offset,
+                                       GLenum texture_target,
+                                       const SkImageInfo& src_info,
+                                       const void* src_pixels) {
+  NOTREACHED();
+}
+
 void RasterImplementation::BeginRasterCHROMIUM(
     GLuint sk_color,
     GLuint msaa_sample_count,
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index cc75dfb..920e96b9 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -122,6 +122,13 @@
                       GLboolean unpack_flip_y,
                       GLboolean unpack_premultiply_alpha) override;
 
+  void WritePixels(const gpu::Mailbox& dest_mailbox,
+                   int dst_x_offset,
+                   int dst_y_offset,
+                   GLenum texture_target,
+                   const SkImageInfo& src_info,
+                   const void* src_pixels) override;
+
   void BeginRasterCHROMIUM(GLuint sk_color,
                            GLuint msaa_sample_count,
                            GLboolean can_use_lcd_text,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 3021d36..3d5e3070 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -31,6 +31,35 @@
 namespace gpu {
 namespace raster {
 
+namespace {
+
+GLenum SkColorTypeToGLDataFormat(SkColorType color_type) {
+  switch (color_type) {
+    case kRGBA_8888_SkColorType:
+      return GL_RGBA;
+    case kBGRA_8888_SkColorType:
+      return GL_BGRA_EXT;
+    default:
+      DLOG(ERROR) << "Unknown SkColorType " << color_type;
+  }
+  NOTREACHED();
+  return 0;
+}
+
+GLenum SkColorTypeToGLDataType(SkColorType color_type) {
+  switch (color_type) {
+    case kRGBA_8888_SkColorType:
+    case kBGRA_8888_SkColorType:
+      return GL_UNSIGNED_BYTE;
+    default:
+      DLOG(ERROR) << "Unknown SkColorType " << color_type;
+  }
+  NOTREACHED();
+  return 0;
+}
+
+}  // namespace
+
 RasterImplementationGLES::RasterImplementationGLES(
     gles2::GLES2Interface* gl,
     ContextSupport* context_support)
@@ -134,6 +163,27 @@
   gl_->DeleteTextures(2, texture_ids);
 }
 
+void RasterImplementationGLES::WritePixels(const gpu::Mailbox& dest_mailbox,
+                                           int dst_x_offset,
+                                           int dst_y_offset,
+                                           GLenum texture_target,
+                                           const SkImageInfo& src_info,
+                                           const void* src_pixels) {
+  GLuint texture_id = CreateAndConsumeForGpuRaster(dest_mailbox);
+  BeginSharedImageAccessDirectCHROMIUM(
+      texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+
+  gl_->BindTexture(texture_target, texture_id);
+  gl_->TexSubImage2D(texture_target, 0, dst_x_offset, dst_y_offset,
+                     src_info.width(), src_info.height(),
+                     SkColorTypeToGLDataFormat(src_info.colorType()),
+                     SkColorTypeToGLDataType(src_info.colorType()), src_pixels);
+  gl_->BindTexture(texture_target, 0);
+
+  EndSharedImageAccessDirectCHROMIUM(texture_id);
+  DeleteGpuRasterTexture(texture_id);
+}
+
 void RasterImplementationGLES::BeginRasterCHROMIUM(
     GLuint sk_color,
     GLuint msaa_sample_count,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index 164b69a7..4295a36 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -69,6 +69,13 @@
                       GLboolean unpack_flip_y,
                       GLboolean unpack_premultiply_alpha) override;
 
+  void WritePixels(const gpu::Mailbox& dest_mailbox,
+                   int dst_x_offset,
+                   int dst_y_offset,
+                   GLenum texture_target,
+                   const SkImageInfo& src_info,
+                   const void* src_pixels) override;
+
   // OOP-Raster
   void BeginRasterCHROMIUM(GLuint sk_color,
                            GLuint msaa_sample_count,
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index 4685633..209dd65 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -12,6 +12,8 @@
 #include "gpu/command_buffer/client/interface_base.h"
 #include "gpu/command_buffer/common/sync_token.h"
 
+struct SkImageInfo;
+
 namespace cc {
 class DisplayItemList;
 class ImageProvider;
@@ -53,6 +55,14 @@
                               GLsizei height,
                               GLboolean unpack_flip_y,
                               GLboolean unpack_premultiply_alpha) = 0;
+
+  virtual void WritePixels(const gpu::Mailbox& dest_mailbox,
+                           int dst_x_offset,
+                           int dst_y_offset,
+                           GLenum texture_target,
+                           const SkImageInfo& src_info,
+                           const void* src_pixels) = 0;
+
   // OOP-Raster
   virtual void BeginRasterCHROMIUM(GLuint sk_color,
                                    GLuint msaa_sample_count,
diff --git a/gpu/vulkan/android/vulkan_android_unittests.cc b/gpu/vulkan/android/vulkan_android_unittests.cc
index f406a344..9bd5983d 100644
--- a/gpu/vulkan/android/vulkan_android_unittests.cc
+++ b/gpu/vulkan/android/vulkan_android_unittests.cc
@@ -130,7 +130,7 @@
   const gfx::Size size(hwb_desc.width, hwb_desc.height);
   auto* device_queue = vk_context_provider_->GetDeviceQueue();
   auto handle = base::android::ScopedHardwareBufferHandle::Adopt(buffer);
-  gfx::GpuMemoryBufferHandle gmp_handle(std::move(handle));
+  gfx::GpuMemoryBufferHandle gmb_handle(std::move(handle));
   auto vulkan_image = VulkanImage::CreateFromGpuMemoryBufferHandle(
       device_queue, std::move(gmb_handle), size, VK_FORMAT_R8G8B8A8_UNORM,
       0 /* usage */);
diff --git a/infra/config/buckets/ci.star b/infra/config/buckets/ci.star
index c2d284b4..dc78bde 100644
--- a/infra/config/buckets/ci.star
+++ b/infra/config/buckets/ci.star
@@ -1027,7 +1027,6 @@
 ci.fyi_ios_builder(
     name = 'ios13-beta-simulator',
     executable = 'recipe:chromium',
-    goma_backend = goma.backend.RBE_PROD,
     properties = {
         'xcode_build_version': '11c29',
     },
@@ -1044,7 +1043,6 @@
 ci.fyi_ios_builder(
     name = 'ios13-sdk-simulator',
     executable = 'recipe:chromium',
-    goma_backend = goma.backend.RBE_PROD,
     properties = {
         'xcode_build_version': '11c29'
     }
diff --git a/infra/config/consoles/chromium.goma.migration.star b/infra/config/consoles/chromium.goma.migration.star
index e597b3b..ca9a70df 100644
--- a/infra/config/consoles/chromium.goma.migration.star
+++ b/infra/config/consoles/chromium.goma.migration.star
@@ -1194,5 +1194,25 @@
             category = 'ios|week1b|m81',
             short_name = 'sim',
         ),
+        luci.console_view_entry(
+            builder = 'ci/ios-simulator-cronet',
+            category = 'ios|week1c',
+            short_name = 'cro',
+        ),
+        luci.console_view_entry(
+            builder = 'ci/ios-simulator-cr-recipe',
+            category = 'ios|week1c',
+            short_name = 'crr',
+        ),
+        luci.console_view_entry(
+            builder = 'ci/ios-webkit-tot',
+            category = 'ios|week1c',
+            short_name = 'webkit',
+        ),
+        luci.console_view_entry(
+            builder = 'ci/ios13-sdk-device',
+            category = 'ios|week1c|ios13',
+            short_name = 'dev',
+        ),
     ],
 )
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index a126d2b..2a1cc1c 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -6924,6 +6924,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.fyi\""
         properties_j: "xcode_build_version:\"11a1027\""
@@ -6947,6 +6948,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.fyi\""
         properties_j: "xcode_build_version:\"11c29\""
@@ -7018,6 +7020,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.fyi\""
         properties_j: "xcode_build_version:\"11c505wk\""
@@ -7065,6 +7068,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.fyi\""
         properties_j: "xcode_build_version:\"11c29\""
@@ -15553,7 +15557,7 @@
       dimensions: "cpu:x86-64"
       dimensions: "os:Mac"
       recipe: <
-        name: "chromium_trybot"
+        name: "ios/try"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
         properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 1f26561c..cd76e21 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -5525,6 +5525,26 @@
     category: "ios|week1b|m81"
     short_name: "sim"
   >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/ios-simulator-cronet"
+    category: "ios|week1c"
+    short_name: "cro"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/ios-simulator-cr-recipe"
+    category: "ios|week1c"
+    short_name: "crr"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/ios-webkit-tot"
+    category: "ios|week1c"
+    short_name: "webkit"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/ios13-sdk-device"
+    category: "ios|week1c|ios13"
+    short_name: "dev"
+  >
   header: <
     oncalls: <
       name: "Chromium"
diff --git a/infra/config/lib/ci.star b/infra/config/lib/ci.star
index 00744b1..1734c61 100644
--- a/infra/config/lib/ci.star
+++ b/infra/config/lib/ci.star
@@ -200,7 +200,7 @@
     name,
     caches = None,
     executable='recipe:ios/unified_builder_tester',
-    goma_backend = None,
+    goma_backend=builders.goma.backend.RBE_PROD,
     **kwargs):
 
   if not caches:
@@ -211,7 +211,6 @@
       caches = caches,
       cores = None,
       executable = executable,
-      goma_backend = goma_backend,
       os = builders.os.MAC_ANY,
       **kwargs
   )
diff --git a/infra/config/versioned/trunk/buckets/try.star b/infra/config/versioned/trunk/buckets/try.star
index 5d1d847..a0da119 100644
--- a/infra/config/versioned/trunk/buckets/try.star
+++ b/infra/config/versioned/trunk/buckets/try.star
@@ -517,7 +517,6 @@
 
 try_.chromium_mac_ios_builder(
     name = 'ios-simulator-full-configs',
-    executable = 'recipe:chromium_trybot',
     tryjob = try_.job(
         location_regexp = [
             '.+/[+]/ios/.+',
diff --git a/ios/chrome/browser/policy/BUILD.gn b/ios/chrome/browser/policy/BUILD.gn
index 7974c18a..a6f4f730 100644
--- a/ios/chrome/browser/policy/BUILD.gn
+++ b/ios/chrome/browser/policy/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/policy:generated",
     "//components/policy/core/common",
     "//ios/chrome/browser",
+    "//ios/chrome/browser:pref_names",
     "//ios/chrome/browser/browser_state",
     "//services/network/public/cpp",
   ]
@@ -100,6 +101,7 @@
   deps = [
     "//base",
     "//components/strings",
+    "//ios/chrome/browser:pref_names",
     "//ios/chrome/browser:utils",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
     "//ios/testing/earl_grey:eg_test_support+eg2",
@@ -120,7 +122,9 @@
   ]
   deps = [
     ":policy",
+    ":test_support",
     "//base",
+    "//components/policy:generated",
     "//components/policy/core/browser",
     "//components/policy/core/common",
     "//ios/chrome/browser",
diff --git a/ios/chrome/browser/policy/policy_egtest.mm b/ios/chrome/browser/policy/policy_egtest.mm
index c858a79c..3c8a500 100644
--- a/ios/chrome/browser/policy/policy_egtest.mm
+++ b/ios/chrome/browser/policy/policy_egtest.mm
@@ -11,6 +11,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/chrome_switches.h"
 #import "ios/chrome/browser/policy/policy_egtest_app_interface.h"
+#include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #include "ios/chrome/test/earl_grey/chrome_test_case.h"
 #include "ios/testing/earl_grey/app_launch_configuration.h"
@@ -21,8 +22,6 @@
 #error "This file requires ARC support."
 #endif
 
-GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(PolicyEGTestAppInterface)
-
 namespace {
 
 // Returns the value of a given policy, looked up in the current platform policy
@@ -62,6 +61,29 @@
                                                     IDS_POLICY_SHOW_UNSET)];
 }
 
+// Tests for the SearchSuggestEnabled policy.
+- (void)testSearchSuggestEnabled {
+  // Loading chrome://policy isn't necessary for the test to succeed, but it
+  // provides some visual feedback as the test runs.
+  [ChromeEarlGrey loadURL:GURL("chrome://policy")];
+  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
+                                                    IDS_POLICY_SHOW_UNSET)];
+
+  // Verify that the unmanaged pref's default value is true.
+  GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSearchSuggestEnabled],
+                 @"Unexpected default value");
+
+  // Force the preference off via policy.
+  [PolicyEGTestAppInterface setSuggestPolicyEnabled:NO];
+  GREYAssertFalse([ChromeEarlGrey userBooleanPref:prefs::kSearchSuggestEnabled],
+                  @"Search suggest preference was unexpectedly true");
+
+  // Force the preference on via policy.
+  [PolicyEGTestAppInterface setSuggestPolicyEnabled:YES];
+  GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSearchSuggestEnabled],
+                 @"Search suggest preference was unexpectedly false");
+}
+
 @end
 
 // Test case that uses the production platform policy provider.
@@ -121,3 +143,7 @@
 }
 
 @end
+
+// TODO(crbug.com/1015113): This macro breaks Xcode indexing unless it is placed
+// at the bottom of the file or followed by a semicolon.
+GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(PolicyEGTestAppInterface)
diff --git a/ios/chrome/browser/policy/policy_egtest_app_interface.h b/ios/chrome/browser/policy/policy_egtest_app_interface.h
index 3d1febb..9abbdc5 100644
--- a/ios/chrome/browser/policy/policy_egtest_app_interface.h
+++ b/ios/chrome/browser/policy/policy_egtest_app_interface.h
@@ -14,6 +14,12 @@
 // namespace.
 + (NSString*)valueForPlatformPolicy:(NSString*)policyKey;
 
+// Sets the |SearchSuggestEnabled| policy to the given value.
+// TODO(crbug.com/1024115): This should be replaced with a more generic API that
+// can set arbitrarily complex policy data. This suggest-specific API only
+// exists to allow us to write an example policy EG2 test.
++ (void)setSuggestPolicyEnabled:(BOOL)enabled;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_POLICY_POLICY_EGTEST_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/policy/policy_egtest_app_interface.mm b/ios/chrome/browser/policy/policy_egtest_app_interface.mm
index 004f384..59246b1 100644
--- a/ios/chrome/browser/policy/policy_egtest_app_interface.mm
+++ b/ios/chrome/browser/policy/policy_egtest_app_interface.mm
@@ -12,8 +12,11 @@
 #include "components/policy/core/common/policy_bundle.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/policy/browser_policy_connector_ios.h"
+#include "ios/chrome/browser/policy/test_platform_policy_provider.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -62,4 +65,12 @@
   return SerializedValue(policyMap.GetValue(key));
 }
 
++ (void)setSuggestPolicyEnabled:(BOOL)enabled {
+  policy::PolicyMap values;
+  values.Set(policy::key::kSearchSuggestEnabled, policy::POLICY_LEVEL_MANDATORY,
+             policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
+             std::make_unique<base::Value>(enabled), nullptr);
+  GetTestPlatformPolicyProvider()->UpdateChromePolicy(values);
+}
+
 @end
diff --git a/ios/chrome/browser/policy/policy_unittest.mm b/ios/chrome/browser/policy/policy_unittest.mm
index bb5c0d9..db926ec8 100644
--- a/ios/chrome/browser/policy/policy_unittest.mm
+++ b/ios/chrome/browser/policy/policy_unittest.mm
@@ -85,7 +85,7 @@
  protected:
   // Temporary directory to hold preference files.
   base::ScopedTempDir state_directory_;
-  
+
   // The task environment for this test.
   base::test::TaskEnvironment task_environment_;
 
@@ -111,7 +111,23 @@
 // Tests that the SearchSuggestEnabled preference is correctly managed by
 // policy.
 TEST_F(PolicyTest, TestSearchSuggestEnabled) {
-  // This preference is currently not managed.
   EXPECT_FALSE(
       pref_service_->IsManagedPreference(prefs::kSearchSuggestEnabled));
+
+  policy::PolicyMap values;
+  values.Set(policy::key::kSearchSuggestEnabled, policy::POLICY_LEVEL_MANDATORY,
+             policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
+             std::make_unique<base::Value>(true), nullptr);
+  policy_provider_.UpdateChromePolicy(values);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(pref_service_->IsManagedPreference(prefs::kSearchSuggestEnabled));
+  EXPECT_TRUE(pref_service_->GetBoolean(prefs::kSearchSuggestEnabled));
+
+  values.Set(policy::key::kSearchSuggestEnabled, policy::POLICY_LEVEL_MANDATORY,
+             policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
+             std::make_unique<base::Value>(false), nullptr);
+  policy_provider_.UpdateChromePolicy(values);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(pref_service_->IsManagedPreference(prefs::kSearchSuggestEnabled));
+  EXPECT_FALSE(pref_service_->GetBoolean(prefs::kSearchSuggestEnabled));
 }
diff --git a/ios/web/shell/shell_url_request_context_getter.mm b/ios/web/shell/shell_url_request_context_getter.mm
index c7a02d2..82add2fda 100644
--- a/ios/web/shell/shell_url_request_context_getter.mm
+++ b/ios/web/shell/shell_url_request_context_getter.mm
@@ -82,8 +82,8 @@
                                                            user_agent));
     storage_->set_proxy_resolution_service(
         net::ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-            std::move(proxy_config_service_), /*quick_check_enabled=*/true,
-            url_request_context_->net_log()));
+            std::move(proxy_config_service_), url_request_context_->net_log(),
+            /*quick_check_enabled=*/true));
     storage_->set_ssl_config_service(
         std::make_unique<net::SSLConfigServiceDefaults>());
     storage_->set_cert_verifier(
diff --git a/ios/web_view/internal/web_view_url_request_context_getter.mm b/ios/web_view/internal/web_view_url_request_context_getter.mm
index a782c371..5a917b1 100644
--- a/ios/web_view/internal/web_view_url_request_context_getter.mm
+++ b/ios/web_view/internal/web_view_url_request_context_getter.mm
@@ -91,8 +91,8 @@
                                                            user_agent));
     storage_->set_proxy_resolution_service(
         net::ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-            std::move(proxy_config_service_), /*quick_check_enabled=*/true,
-            url_request_context_->net_log()));
+            std::move(proxy_config_service_), url_request_context_->net_log(),
+            /*quick_check_enabled=*/true));
     storage_->set_ssl_config_service(
         std::make_unique<net::SSLConfigServiceDefaults>());
     storage_->set_cert_verifier(
diff --git a/media/learning/common/BUILD.gn b/media/learning/common/BUILD.gn
index 24a2e5d..773e1eb3 100644
--- a/media/learning/common/BUILD.gn
+++ b/media/learning/common/BUILD.gn
@@ -9,8 +9,7 @@
     "//media/learning/impl:*",
     "//media/learning/impl:test_support",
     "//media/learning/mojo/public/cpp:*",
-    "//media/learning/mojo/public/mojom:mojom",
-    "//media/learning/mojo/public/mojom:mojom_blink",
+    "//media/learning/mojo/public/mojom:*",
     "//media/learning/mojo:*",
     "//media/learning/common:unit_tests",
 
diff --git a/media/mojo/services/mojo_cdm_allocator.cc b/media/mojo/services/mojo_cdm_allocator.cc
index 6a3ff14..afdd0a1 100644
--- a/media/mojo/services/mojo_cdm_allocator.cc
+++ b/media/mojo/services/mojo_cdm_allocator.cc
@@ -38,9 +38,14 @@
 
     // cdm::Buffer interface limits capacity to uint32.
     DCHECK_LE(capacity, std::numeric_limits<uint32_t>::max());
-    return new MojoCdmBuffer(std::move(buffer),
-                             base::checked_cast<uint32_t>(capacity),
-                             std::move(mojo_shared_buffer_done_cb));
+
+    auto mapping = buffer->Map(capacity);
+    if (!mapping)
+      return nullptr;
+
+    return new MojoCdmBuffer(
+        std::move(buffer), base::checked_cast<uint32_t>(capacity),
+        std::move(mapping), std::move(mojo_shared_buffer_done_cb));
   }
 
   // cdm::Buffer implementation.
@@ -75,13 +80,13 @@
  private:
   MojoCdmBuffer(mojo::ScopedSharedBufferHandle buffer,
                 uint32_t capacity,
+                mojo::ScopedSharedBufferMapping mapping,
                 MojoSharedBufferVideoFrame::MojoSharedBufferDoneCB
                     mojo_shared_buffer_done_cb)
       : buffer_(std::move(buffer)),
         mojo_shared_buffer_done_cb_(std::move(mojo_shared_buffer_done_cb)),
-        capacity_(capacity),
-        size_(0) {
-    mapping_ = buffer_->Map(capacity_);
+        mapping_(std::move(mapping)),
+        capacity_(capacity) {
     DCHECK(mapping_);
   }
 
@@ -95,8 +100,8 @@
       mojo_shared_buffer_done_cb_;
 
   mojo::ScopedSharedBufferMapping mapping_;
-  uint32_t capacity_;
-  uint32_t size_;
+  const uint32_t capacity_;
+  uint32_t size_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(MojoCdmBuffer);
 };
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index 618a5ba..3ae081a 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -96,6 +96,8 @@
     "message.h",
     "message_header_validator.h",
     "scoped_interface_endpoint_handle.h",
+    "scoped_message_error_crash_key.cc",
+    "scoped_message_error_crash_key.h",
     "string_data_view.h",
     "string_traits.h",
     "string_traits_stl.h",
diff --git a/mojo/public/cpp/bindings/scoped_message_error_crash_key.cc b/mojo/public/cpp/bindings/scoped_message_error_crash_key.cc
new file mode 100644
index 0000000..b942d43
--- /dev/null
+++ b/mojo/public/cpp/bindings/scoped_message_error_crash_key.cc
@@ -0,0 +1,30 @@
+// Copyright 2020 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 "mojo/public/cpp/bindings/scoped_message_error_crash_key.h"
+
+namespace mojo {
+namespace debug {
+
+namespace {
+
+base::debug::CrashKeyString* GetMojoMessageErrorCrashKey() {
+  // The "mojo-message-error" name used below is recognized by Chrome crash
+  // analysis services - please avoid changing the name if possible.
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "mojo-message-error", base::debug::CrashKeySize::Size256);
+  return crash_key;
+}
+
+}  // namespace
+
+ScopedMessageErrorCrashKey::ScopedMessageErrorCrashKey(
+    const std::string& mojo_message_error)
+    : base::debug::ScopedCrashKeyString(GetMojoMessageErrorCrashKey(),
+                                        mojo_message_error) {}
+
+ScopedMessageErrorCrashKey::~ScopedMessageErrorCrashKey() = default;
+
+}  // namespace debug
+}  // namespace mojo
diff --git a/mojo/public/cpp/bindings/scoped_message_error_crash_key.h b/mojo/public/cpp/bindings/scoped_message_error_crash_key.h
new file mode 100644
index 0000000..d7e8223
--- /dev/null
+++ b/mojo/public/cpp/bindings/scoped_message_error_crash_key.h
@@ -0,0 +1,33 @@
+// Copyright 2020 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 MOJO_PUBLIC_CPP_BINDINGS_SCOPED_MESSAGE_ERROR_CRASH_KEY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SCOPED_MESSAGE_ERROR_CRASH_KEY_H_
+
+#include <string>
+
+#include "base/component_export.h"
+#include "base/debug/crash_logging.h"
+
+namespace mojo {
+namespace debug {
+
+// Helper class for storing |mojo_message_error| in the right crash key (when
+// initiating a base::debug::DumpWithoutCrashing because of a bad message
+// report).
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) ScopedMessageErrorCrashKey
+    : public base::debug::ScopedCrashKeyString {
+ public:
+  explicit ScopedMessageErrorCrashKey(const std::string& mojo_message_error);
+  ~ScopedMessageErrorCrashKey();
+
+  ScopedMessageErrorCrashKey(const ScopedMessageErrorCrashKey&) = delete;
+  ScopedMessageErrorCrashKey& operator=(const ScopedMessageErrorCrashKey&) =
+      delete;
+};
+
+}  // namespace debug
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_SCOPED_MESSAGE_ERROR_CRASH_KEY_H_
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 1051c30..d6cd4a4 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -1465,7 +1465,7 @@
 
       java_srcjar_target_name = target_name + "_java_sources"
       action(java_srcjar_target_name) {
-        script = "//mojo/public/tools/gn/zip.py"
+        script = "//build/android/gyp/zip.py"
         inputs = []
         if (output_file_base_paths != []) {
           foreach(base_path, output_file_base_paths) {
@@ -1477,7 +1477,7 @@
         rebase_inputs = rebase_path(inputs, root_build_dir)
         rebase_output = rebase_path(output, root_build_dir)
         args = [
-          "--zip-inputs=$rebase_inputs",
+          "--input-zips=$rebase_inputs",
           "--output=$rebase_output",
         ]
         deps = []
diff --git a/mojo/public/tools/gn/zip.py b/mojo/public/tools/gn/zip.py
deleted file mode 100755
index adc9cb1c..0000000
--- a/mojo/public/tools/gn/zip.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# TODO(brettw) bug 582594: merge this with build/android/gn/zip.py and update
-# callers to use the existing template rather than invoking this directly.
-
-"""Archives a set of files.
-"""
-
-import optparse
-import os
-import sys
-import zipfile
-
-sys.path.append(os.path.join(os.path.dirname(__file__),
-                             os.pardir, os.pardir, os.pardir, os.pardir,
-                             "build"))
-import gn_helpers
-
-sys.path.append(os.path.join(os.path.dirname(__file__),
-                             os.pardir, os.pardir, os.pardir, os.pardir,
-                             'build', 'android', 'gyp'))
-from util import build_utils
-
-
-def DoZip(inputs, link_inputs, zip_inputs, output, base_dir):
-  files = []
-  with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as outfile:
-    for f in inputs:
-      file_name = os.path.relpath(f, base_dir)
-      files.append(file_name)
-      build_utils.AddToZipHermetic(outfile, file_name, f)
-    for f in link_inputs:
-      realf = os.path.realpath(f)  # Resolve symlinks.
-      file_name = os.path.relpath(realf, base_dir)
-      files.append(file_name)
-      build_utils.AddToZipHermetic(outfile, file_name, realf)
-    for zf_name in zip_inputs:
-      with zipfile.ZipFile(zf_name, 'r') as zf:
-        for f in zf.namelist():
-          if f not in files:
-            files.append(f)
-            build_utils.AddToZipHermetic(outfile, f, data=zf.read(f))
-
-
-def main():
-  parser = optparse.OptionParser()
-
-  parser.add_option('--inputs',
-      help='GN format list of files to archive.')
-  parser.add_option('--link-inputs',
-      help='GN-format list of files to archive. Symbolic links are resolved.')
-  parser.add_option('--zip-inputs',
-      help='GN-format list of zip files to re-archive.')
-  parser.add_option('--output', help='Path to output archive.')
-  parser.add_option('--base-dir',
-                    help='If provided, the paths in the archive will be '
-                    'relative to this directory', default='.')
-
-  options, _ = parser.parse_args()
-
-  inputs = []
-  if (options.inputs):
-    parser = gn_helpers.GNValueParser(options.inputs)
-    inputs = parser.ParseList()
-
-  link_inputs = []
-  if options.link_inputs:
-    parser = gn_helpers.GNValueParser(options.link_inputs)
-    link_inputs = parser.ParseList()
-
-  zip_inputs = []
-  if options.zip_inputs:
-    parser = gn_helpers.GNValueParser(options.zip_inputs)
-    zip_inputs = parser.ParseList()
-
-  output = options.output
-  base_dir = options.base_dir
-
-  DoZip(inputs, link_inputs, zip_inputs, output, base_dir)
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index abbce47..86d5348 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -5586,7 +5586,7 @@
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               ProxyConfig::CreateAutoDetect(), TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<SameProxyWithDifferentSchemesProxyResolverFactory>(),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
 
   std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
 
@@ -6007,7 +6007,7 @@
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
@@ -6611,7 +6611,7 @@
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
@@ -7150,7 +7150,7 @@
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
 
@@ -7285,7 +7285,7 @@
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
 
   std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
   // Fetch https://proxy:70/ via HTTP/2.
@@ -14184,7 +14184,7 @@
   session_deps_.proxy_resolution_service =
       std::make_unique<ConfiguredProxyResolutionService>(
           std::move(proxy_config_service), std::move(proxy_resolver_factory),
-          &net_log);
+          &net_log, /*quick_check_enabled=*/true);
 
   session_deps_.net_log = &net_log;
 
@@ -14265,7 +14265,7 @@
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
           std::make_unique<CapturingProxyResolverFactory>(
               &capturing_proxy_resolver),
-          nullptr);
+          nullptr, /*quick_check_enabled=*/true);
   RecordingTestNetLog net_log;
   session_deps_.net_log = &net_log;
 
@@ -17647,7 +17647,7 @@
       std::make_unique<ConfiguredProxyResolutionService>(
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
-          nullptr, nullptr);
+          nullptr, nullptr, /*quick_check_enabled=*/true);
 
   SSLSocketDataProvider ssl1(ASYNC, OK);  // to the proxy
   ssl1.next_proto = kProtoHTTP2;
@@ -19290,7 +19290,8 @@
       new ConfiguredProxyResolutionService(
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
-          std::make_unique<FailingProxyResolverFactory>(), nullptr));
+          std::make_unique<FailingProxyResolverFactory>(), nullptr,
+          /*quick_check_enabled=*/true));
 
   HttpRequestInfo request;
   request.method = "GET";
@@ -19320,7 +19321,8 @@
       new ConfiguredProxyResolutionService(
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
-          base::WrapUnique(proxy_resolver_factory), nullptr));
+          base::WrapUnique(proxy_resolver_factory), nullptr,
+          /*quick_check_enabled=*/true));
   HttpRequestInfo request;
   request.method = "GET";
   request.url = GURL("http://www.example.org/");
diff --git a/net/http/http_stream_factory_job_controller_unittest.cc b/net/http/http_stream_factory_job_controller_unittest.cc
index 1dfc39f5..ed9d68ed 100644
--- a/net/http/http_stream_factory_job_controller_unittest.cc
+++ b/net/http/http_stream_factory_job_controller_unittest.cc
@@ -363,7 +363,8 @@
       new ConfiguredProxyResolutionService(
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
-          std::make_unique<FailingProxyResolverFactory>(), nullptr));
+          std::make_unique<FailingProxyResolverFactory>(), nullptr,
+          /*quick_check_enabled=*/true));
   HttpRequestInfo request_info;
   request_info.method = "GET";
   request_info.url = GURL("http://www.google.com");
@@ -401,7 +402,8 @@
       new ConfiguredProxyResolutionService(
           std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
               proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
-          base::WrapUnique(proxy_resolver_factory), nullptr));
+          base::WrapUnique(proxy_resolver_factory), nullptr,
+          /*quick_check_enabled=*/true));
   HttpRequestInfo request_info;
   request_info.method = "GET";
   request_info.url = GURL("http://www.google.com");
diff --git a/net/proxy_resolution/configured_proxy_resolution_service.cc b/net/proxy_resolution/configured_proxy_resolution_service.cc
index 33f1061d..d4dc748c 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_service.cc
@@ -850,7 +850,8 @@
 ConfiguredProxyResolutionService::ConfiguredProxyResolutionService(
     std::unique_ptr<ProxyConfigService> config_service,
     std::unique_ptr<ProxyResolverFactory> resolver_factory,
-    NetLog* net_log)
+    NetLog* net_log,
+    bool quick_check_enabled)
     : config_service_(std::move(config_service)),
       resolver_factory_(std::move(resolver_factory)),
       current_state_(STATE_NONE),
@@ -858,7 +859,7 @@
       net_log_(net_log),
       stall_proxy_auto_config_delay_(
           TimeDelta::FromMilliseconds(kDelayAfterNetworkChangesMs)),
-      quick_check_enabled_(true) {
+      quick_check_enabled_(quick_check_enabled) {
   NetworkChangeNotifier::AddIPAddressObserver(this);
   NetworkChangeNotifier::AddDNSObserver(this);
   config_service_->AddObserver(this);
@@ -868,8 +869,8 @@
 std::unique_ptr<ConfiguredProxyResolutionService>
 ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
     std::unique_ptr<ProxyConfigService> proxy_config_service,
-    bool quick_check_enabled,
-    NetLog* net_log) {
+    NetLog* net_log,
+    bool quick_check_enabled) {
   DCHECK(proxy_config_service);
 
   if (!ProxyResolverFactoryForSystem::IsSupported()) {
@@ -882,8 +883,7 @@
           std::move(proxy_config_service),
           std::make_unique<ProxyResolverFactoryForSystem>(
               kDefaultNumPacThreads),
-          net_log);
-  proxy_resolution_service->set_quick_check_enabled(quick_check_enabled);
+          net_log, quick_check_enabled);
   return proxy_resolution_service;
 }
 
@@ -894,7 +894,8 @@
     NetLog* net_log) {
   return std::make_unique<ConfiguredProxyResolutionService>(
       std::move(proxy_config_service),
-      std::make_unique<ProxyResolverFactoryForNullResolver>(), net_log);
+      std::make_unique<ProxyResolverFactoryForNullResolver>(), net_log,
+      /*quick_check_enabled=*/false);
 }
 
 // static
@@ -904,8 +905,8 @@
   // TODO(eroman): This isn't quite right, won't work if |pc| specifies
   //               a PAC script.
   return CreateUsingSystemProxyResolver(
-      std::make_unique<ProxyConfigServiceFixed>(pc),
-      /*quick_check_enabled=*/true, nullptr);
+      std::make_unique<ProxyConfigServiceFixed>(pc), nullptr,
+      /*quick_check_enabled=*/true);
 }
 
 // static
@@ -925,7 +926,8 @@
   // Use direct connections.
   return std::make_unique<ConfiguredProxyResolutionService>(
       std::make_unique<ProxyConfigServiceDirect>(),
-      std::make_unique<ProxyResolverFactoryForNullResolver>(), nullptr);
+      std::make_unique<ProxyResolverFactoryForNullResolver>(), nullptr,
+      /*quick_check_enabled=*/true);
 }
 
 // static
@@ -943,7 +945,8 @@
 
   return std::make_unique<ConfiguredProxyResolutionService>(
       std::move(proxy_config_service),
-      std::make_unique<ProxyResolverFactoryForPacResult>(pac_string), nullptr);
+      std::make_unique<ProxyResolverFactoryForPacResult>(pac_string), nullptr,
+      /*quick_check_enabled=*/true);
 }
 
 // static
@@ -957,7 +960,8 @@
 
   return std::make_unique<ConfiguredProxyResolutionService>(
       std::move(proxy_config_service),
-      std::make_unique<ProxyResolverFactoryForPacResult>(pac_string), nullptr);
+      std::make_unique<ProxyResolverFactoryForPacResult>(pac_string), nullptr,
+      /*quick_check_enabled=*/true);
 }
 
 int ConfiguredProxyResolutionService::ResolveProxy(
@@ -997,10 +1001,9 @@
     return rv;
   }
 
-  std::unique_ptr<ConfiguredProxyResolutionRequest> req =
-      std::make_unique<ConfiguredProxyResolutionRequest>(
-          this, url, method, network_isolation_key, result, std::move(callback),
-          net_log);
+  auto req = std::make_unique<ConfiguredProxyResolutionRequest>(
+      this, url, method, network_isolation_key, result, std::move(callback),
+      net_log);
 
   if (current_state_ == STATE_READY) {
     // Start the resolve request.
diff --git a/net/proxy_resolution/configured_proxy_resolution_service.h b/net/proxy_resolution/configured_proxy_resolution_service.h
index 145eaa26b..fe8755d 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service.h
+++ b/net/proxy_resolution/configured_proxy_resolution_service.h
@@ -107,7 +107,8 @@
   ConfiguredProxyResolutionService(
       std::unique_ptr<ProxyConfigService> config_service,
       std::unique_ptr<ProxyResolverFactory> resolver_factory,
-      NetLog* net_log);
+      NetLog* net_log,
+      bool quick_check_enabled);
 
   ~ConfiguredProxyResolutionService() override;
 
@@ -186,8 +187,8 @@
   static std::unique_ptr<ConfiguredProxyResolutionService>
   CreateUsingSystemProxyResolver(
       std::unique_ptr<ProxyConfigService> proxy_config_service,
-      bool quick_check_enabled,
-      NetLog* net_log);
+      NetLog* net_log,
+      bool quick_check_enabled);
 
   // Creates a ConfiguredProxyResolutionService without support for proxy
   // autoconfig.
@@ -249,7 +250,6 @@
   // ConfiguredProxyResolutionService.
   static std::unique_ptr<PacPollPolicy> CreateDefaultPacPollPolicy();
 
-  void set_quick_check_enabled(bool value) { quick_check_enabled_ = value; }
   bool quick_check_enabled_for_testing() const { return quick_check_enabled_; }
 
  private:
diff --git a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
index e82c863a..8fb8c820 100644
--- a/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
+++ b/net/proxy_resolution/configured_proxy_resolution_service_unittest.cc
@@ -389,7 +389,7 @@
       new MockAsyncProxyResolverFactory(false);
   ConfiguredProxyResolutionService service(
       std::make_unique<MockProxyConfigService>(ProxyConfig::CreateDirect()),
-      base::WrapUnique(factory), nullptr);
+      base::WrapUnique(factory), nullptr, /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -427,7 +427,8 @@
   config.proxy_rules().bypass_rules.ParseFromString("*.org");
 
   ConfiguredProxyResolutionService service(
-      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+      /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
   GURL bypass_url("http://internet.org");
@@ -494,7 +495,8 @@
   config.proxy_rules().bypass_rules.ParseFromString("*.org");
 
   ConfiguredProxyResolutionService service(
-      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+      /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
   GURL bypass_url("http://internet.org");
@@ -576,7 +578,8 @@
 
   std::unique_ptr<ConfiguredProxyResolutionService> service =
       std::make_unique<ConfiguredProxyResolutionService>(
-          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr,
+          /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
   GURL url2("http://www.example.com/");
@@ -642,7 +645,8 @@
 
   std::unique_ptr<ConfiguredProxyResolutionService> service =
       std::make_unique<ConfiguredProxyResolutionService>(
-          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr,
+          /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -692,7 +696,8 @@
 
   std::unique_ptr<ConfiguredProxyResolutionService> service =
       std::make_unique<ConfiguredProxyResolutionService>(
-          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr,
+          /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
   ProxyInfo info;
@@ -760,7 +765,8 @@
 
   std::unique_ptr<ConfiguredProxyResolutionService> service =
       std::make_unique<ConfiguredProxyResolutionService>(
-          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr,
+          /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
   ProxyInfo info;
@@ -813,8 +819,9 @@
 
   int rv;
   {
-    ConfiguredProxyResolutionService service(
-        base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+    ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
+                                             base::WrapUnique(factory), nullptr,
+                                             /*quick_check_enabled=*/true);
     rv = service.ResolveProxy(url, std::string(), NetworkIsolationKey(), &info,
                               callback.callback(), &request, log.bound());
     EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
@@ -845,7 +852,8 @@
 
   std::unique_ptr<ConfiguredProxyResolutionService> service =
       std::make_unique<ConfiguredProxyResolutionService>(
-          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr);
+          base::WrapUnique(config_service), base::WrapUnique(factory), nullptr,
+          /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -892,7 +900,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -956,7 +965,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://username:password@www.google.com/?ref#hash#hash");
 
@@ -989,7 +999,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1038,7 +1049,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://this-causes-js-error/");
 
@@ -1097,7 +1109,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1154,7 +1167,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(false);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Resolve something.
   GURL url("http://www.google.com/");
@@ -1195,7 +1209,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start first resolve request.
   GURL url("http://www.google.com/");
@@ -1260,7 +1275,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start first resolve request.
   GURL url("http://www.google.com/");
@@ -1332,7 +1348,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start two resolve requests.
   GURL url1("http://www.google.com/");
@@ -1406,7 +1423,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start first resolve request.
   GURL url("http://www.google.com/");
@@ -1456,7 +1474,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -1508,7 +1527,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start first resolve request.
   GURL url("http://www.google.com/");
@@ -1569,7 +1589,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1707,7 +1728,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1770,7 +1792,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1876,7 +1899,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -1974,7 +1998,8 @@
   config.proxy_rules().bypass_rules.ParseFromString("*.org");
 
   ConfiguredProxyResolutionService service(
-      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+      /*quick_check_enabled=*/true);
 
   int rv;
   GURL url1("http://www.webkit.org");
@@ -2017,7 +2042,8 @@
   EXPECT_EQ(3u, additional_bad_proxies.size());
 
   ConfiguredProxyResolutionService service(
-      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+      std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+      /*quick_check_enabled=*/true);
   ProxyInfo proxy_info;
   proxy_info.UseProxyList(proxy_list);
   const ProxyRetryInfoMap& retry_info = service.proxy_retry_info();
@@ -2038,7 +2064,8 @@
   std::unique_ptr<ProxyResolutionRequest> request;
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("http://www.msn.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2051,7 +2078,8 @@
   }
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("ftp://ftp.google.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2064,7 +2092,8 @@
   }
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("https://webbranch.techcu.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2078,7 +2107,8 @@
   {
     config.proxy_rules().ParseFromString("foopy1:8080");
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("http://www.microsoft.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2101,7 +2131,8 @@
     ProxyConfig config;
     config.proxy_rules().ParseFromString("https=foopy2:8080");
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("http://www.google.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2117,7 +2148,8 @@
     ProxyConfig config;
     config.proxy_rules().ParseFromString("https=foopy2:8080");
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("https://www.google.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2132,7 +2164,8 @@
   {
     ProxyConfig config;
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("http://www.google.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2158,7 +2191,8 @@
   std::unique_ptr<ProxyResolutionRequest> request;
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("http://www.msn.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2171,7 +2205,8 @@
   }
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("ftp://ftp.google.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2184,7 +2219,8 @@
   }
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("https://webbranch.techcu.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2197,7 +2233,8 @@
   }
   {
     ConfiguredProxyResolutionService service(
-        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr);
+        std::make_unique<MockProxyConfigService>(config), nullptr, nullptr,
+        /*quick_check_enabled=*/true);
     GURL test_url("unknown://www.microsoft.com");
     ProxyInfo info;
     TestCompletionCallback callback;
@@ -2223,7 +2260,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start 3 requests.
 
@@ -2297,7 +2335,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2404,7 +2443,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2467,7 +2507,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2568,7 +2609,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2654,7 +2696,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2733,7 +2776,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2796,7 +2840,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2868,7 +2913,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -2906,7 +2952,8 @@
       new MockAsyncProxyResolverFactory(false);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   GURL url("http://www.google.com/");
 
@@ -2932,7 +2979,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(false);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   // Start 1 request.
 
@@ -2988,7 +3036,8 @@
   RecordingTestNetLog log;
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), &log);
+                                           base::WrapUnique(factory), &log,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3111,7 +3160,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3221,7 +3271,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3337,7 +3388,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3449,7 +3501,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3623,7 +3676,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3731,7 +3785,8 @@
     factory = new MockAsyncProxyResolverFactory(false);
 
     service_.reset(new ConfiguredProxyResolutionService(
-        std::move(config_service), base::WrapUnique(factory), nullptr));
+        std::move(config_service), base::WrapUnique(factory), nullptr,
+        /*quick_check_enabled=*/true));
 
     // Do an initial request to initialize the service (configure the PAC
     // script).
@@ -3894,7 +3949,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3927,7 +3983,8 @@
       new MockAsyncProxyResolverFactory(true);
 
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
@@ -3955,7 +4012,8 @@
 
   ConfiguredProxyResolutionService service(
       base::WrapUnique(config_service),
-      std::make_unique<MockAsyncProxyResolverFactory>(false), nullptr);
+      std::make_unique<MockAsyncProxyResolverFactory>(false), nullptr,
+      /*quick_check_enabled=*/true);
 
   pac_histogram.VerifyHistogram();
 
@@ -4066,7 +4124,8 @@
   MockAsyncProxyResolverFactory* factory =
       new MockAsyncProxyResolverFactory(true);
   ConfiguredProxyResolutionService service(base::WrapUnique(config_service),
-                                           base::WrapUnique(factory), nullptr);
+                                           base::WrapUnique(factory), nullptr,
+                                           /*quick_check_enabled=*/true);
 
   MockPacFileFetcher* fetcher = new MockPacFileFetcher;
   service.SetPacFileFetchers(base::WrapUnique(fetcher),
diff --git a/net/url_request/url_request_context_builder.cc b/net/url_request/url_request_context_builder.cc
index 2e255cb..6bc7ae4 100644
--- a/net/url_request/url_request_context_builder.cc
+++ b/net/url_request/url_request_context_builder.cc
@@ -498,17 +498,7 @@
     proxy_resolution_service_ = CreateProxyResolutionService(
         std::move(proxy_config_service_), context.get(),
         context->host_resolver(), context->network_delegate(),
-        context->net_log());
-
-    // Although CreateProxyResolutionService() tries its best to set the PAC
-    // quick check flag, it may be overridden by a child class. We should make
-    // sure we set that value here if applicable.
-    ConfiguredProxyResolutionService* configured_proxy_resolution_service =
-        nullptr;
-    if (proxy_resolution_service_->CastToConfiguredProxyResolutionService(
-            &configured_proxy_resolution_service))
-      configured_proxy_resolution_service->set_quick_check_enabled(
-          pac_quick_check_enabled_);
+        context->net_log(), pac_quick_check_enabled_);
   }
   ProxyResolutionService* proxy_resolution_service =
       proxy_resolution_service_.get();
@@ -634,9 +624,10 @@
     URLRequestContext* url_request_context,
     HostResolver* host_resolver,
     NetworkDelegate* network_delegate,
-    NetLog* net_log) {
+    NetLog* net_log,
+    bool pac_quick_check_enabled) {
   return ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-      std::move(proxy_config_service), pac_quick_check_enabled_, net_log);
+      std::move(proxy_config_service), net_log, pac_quick_check_enabled);
 }
 
 }  // namespace net
diff --git a/net/url_request/url_request_context_builder.h b/net/url_request/url_request_context_builder.h
index f3ab423..1c21cbe3 100644
--- a/net/url_request/url_request_context_builder.h
+++ b/net/url_request/url_request_context_builder.h
@@ -326,7 +326,8 @@
       URLRequestContext* url_request_context,
       HostResolver* host_resolver,
       NetworkDelegate* network_delegate,
-      NetLog* net_log);
+      NetLog* net_log,
+      bool pac_quick_check_enabled);
 
  private:
   std::string name_;
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 86e2580..8a7c393 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -465,8 +465,8 @@
       ProxyConfigWithAnnotation(proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
   std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
       ConfiguredProxyResolutionService::CreateUsingSystemProxyResolver(
-          std::move(proxy_config_service), /*quick_check_enabled=*/true,
-          NetLog::Get()));
+          std::move(proxy_config_service), NetLog::Get(),
+          /*quick_check_enabled=*/true));
   ASSERT_EQ(ws_server.host_port_pair().host(), "127.0.0.1");
   context_.set_proxy_resolution_service(proxy_resolution_service.get());
   InitialiseContext();
diff --git a/printing/BUILD.gn b/printing/BUILD.gn
index faf4aea..3f1b12f 100644
--- a/printing/BUILD.gn
+++ b/printing/BUILD.gn
@@ -236,9 +236,9 @@
       "backend/printing_restrictions.cc",
       "backend/printing_restrictions.h",
       "printed_document_chromeos.cc",
-      "printer_query_result_chromeos.h",
-      "printer_status_chromeos.cc",
-      "printer_status_chromeos.h",
+      "printer_query_result.h",
+      "printer_status.cc",
+      "printer_status.h",
       "printing_context_no_system_dialog.cc",
       "printing_context_no_system_dialog.h",
     ]
diff --git a/printing/backend/cups_connection.h b/printing/backend/cups_connection.h
index 439904df..bf0e616 100644
--- a/printing/backend/cups_connection.h
+++ b/printing/backend/cups_connection.h
@@ -16,7 +16,7 @@
 #include "printing/backend/cups_deleters.h"
 #include "printing/backend/cups_jobs.h"
 #include "printing/backend/cups_printer.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 #include "printing/printing_export.h"
 #include "url/gurl.h"
 
diff --git a/printing/backend/cups_jobs.cc b/printing/backend/cups_jobs.cc
index d87f3af..5e58a45 100644
--- a/printing/backend/cups_jobs.cc
+++ b/printing/backend/cups_jobs.cc
@@ -18,7 +18,7 @@
 #include "base/version.h"
 #include "printing/backend/cups_deleters.h"
 #include "printing/backend/cups_ipp_helper.h"
-#include "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 
 namespace printing {
 namespace {
diff --git a/printing/backend/cups_jobs.h b/printing/backend/cups_jobs.h
index d868b547..2cf0c596 100644
--- a/printing/backend/cups_jobs.h
+++ b/printing/backend/cups_jobs.h
@@ -14,7 +14,7 @@
 #include <vector>
 
 #include "base/version.h"
-#include "printing/printer_query_result_chromeos.h"
+#include "printing/printer_query_result.h"
 #include "printing/printing_export.h"
 
 // This file contains a collection of functions used to query IPP printers or
diff --git a/printing/printer_query_result_chromeos.h b/printing/printer_query_result.h
similarity index 74%
rename from printing/printer_query_result_chromeos.h
rename to printing/printer_query_result.h
index 8f926a8..8dfe86a 100644
--- a/printing/printer_query_result_chromeos.h
+++ b/printing/printer_query_result.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 PRINTING_PRINTER_QUERY_RESULT_CHROMEOS_H_
-#define PRINTING_PRINTER_QUERY_RESULT_CHROMEOS_H_
+#ifndef PRINTING_PRINTER_QUERY_RESULT_H_
+#define PRINTING_PRINTER_QUERY_RESULT_H_
 
 #include "printing/printing_export.h"
 
@@ -18,4 +18,4 @@
 
 }  // namespace printing
 
-#endif  // PRINTING_PRINTER_QUERY_RESULT_CHROMEOS_H_
+#endif  // PRINTING_PRINTER_QUERY_RESULT_H_
diff --git a/printing/printer_status_chromeos.cc b/printing/printer_status.cc
similarity index 88%
rename from printing/printer_status_chromeos.cc
rename to printing/printer_status.cc
index 1425152..cb5d371 100644
--- a/printing/printer_status_chromeos.cc
+++ b/printing/printer_status.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 "printing/printer_status_chromeos.h"
+#include "printing/printer_status.h"
 
 namespace printing {
 
diff --git a/printing/printer_status_chromeos.h b/printing/printer_status.h
similarity index 92%
rename from printing/printer_status_chromeos.h
rename to printing/printer_status.h
index 00dd445..929300b0 100644
--- a/printing/printer_status_chromeos.h
+++ b/printing/printer_status.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 PRINTING_PRINTER_STATUS_CHROMEOS_H_
-#define PRINTING_PRINTER_STATUS_CHROMEOS_H_
+#ifndef PRINTING_PRINTER_STATUS_H_
+#define PRINTING_PRINTER_STATUS_H_
 
 #include <cups/cups.h>
 
@@ -77,4 +77,4 @@
 
 }  // namespace printing
 
-#endif  // PRINTING_PRINTER_STATUS_CHROMEOS_H_
+#endif  // PRINTING_PRINTER_STATUS_H_
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index c9b19f4..fd4118bb 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -2504,10 +2504,90 @@
             proxy_resolver_factory.network_isolation_key());
 }
 
+// Test mojom::ProxyResolver that completes calls to GetProxyForUrl() with a
+// DIRECT "proxy". It additionally emits a script error on line 42 for every
+// call to GetProxyForUrl().
+class MockMojoProxyResolver : public proxy_resolver::mojom::ProxyResolver {
+ public:
+  MockMojoProxyResolver() {}
+
+ private:
+  // Overridden from proxy_resolver::mojom::ProxyResolver:
+  void GetProxyForUrl(
+      const GURL& url,
+      const net::NetworkIsolationKey& network_isolation_key,
+      mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverRequestClient>
+          pending_client) override {
+    // Report a Javascript error and then complete the request successfully,
+    // having chosen DIRECT connections.
+    mojo::Remote<proxy_resolver::mojom::ProxyResolverRequestClient> client(
+        std::move(pending_client));
+    client->OnError(42, "Failed: FindProxyForURL(url=" + url.spec() + ")");
+
+    net::ProxyInfo result;
+    result.UseDirect();
+
+    client->ReportResult(net::OK, result);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(MockMojoProxyResolver);
+};
+
+// Test mojom::ProxyResolverFactory implementation that successfully completes
+// any CreateResolver() requests, and binds the request to a new
+// MockMojoProxyResolver.
+class MockMojoProxyResolverFactory
+    : public proxy_resolver::mojom::ProxyResolverFactory {
+ public:
+  MockMojoProxyResolverFactory() {}
+
+  // Binds and returns a mock ProxyResolverFactory whose lifetime is bound to
+  // the message pipe.
+  static mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverFactory>
+  Create() {
+    mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverFactory> remote;
+    mojo::MakeSelfOwnedReceiver(
+        std::make_unique<MockMojoProxyResolverFactory>(),
+        remote.InitWithNewPipeAndPassReceiver());
+    return remote;
+  }
+
+ private:
+  void CreateResolver(
+      const std::string& pac_url,
+      mojo::PendingReceiver<proxy_resolver::mojom::ProxyResolver> receiver,
+      mojo::PendingRemote<
+          proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
+          pending_client) override {
+    // Bind |receiver| to a new MockMojoProxyResolver, and return success.
+    mojo::MakeSelfOwnedReceiver(std::make_unique<MockMojoProxyResolver>(),
+                                std::move(receiver));
+
+    mojo::Remote<proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
+        client(std::move(pending_client));
+    client->ReportResult(net::OK);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(MockMojoProxyResolverFactory);
+};
+
 TEST_F(NetworkContextTest, PacQuickCheck) {
   // Check the default value.
+  // Note that unless we explicitly create a proxy resolver factory, the code
+  // will assume that we should use a system proxy resolver (i.e. use system
+  // APIs to resolve a proxy). This isn't supported on all platforms. On
+  // unsupported platforms, we'd simply ignore the PAC quick check input and
+  // default to false.
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+#if defined(OS_CHROMEOS)
+  context_params->dhcp_wpad_url_client =
+      network::MockMojoDhcpWpadUrlClient::CreateWithSelfOwnedReceiver(
+          std::string());
+#endif  // defined(OS_CHROMEOS)
+  context_params->proxy_resolver_factory =
+      MockMojoProxyResolverFactory::Create();
   std::unique_ptr<NetworkContext> network_context =
-      CreateContextWithParams(CreateContextParams());
+      CreateContextWithParams(std::move(context_params));
   net::ConfiguredProxyResolutionService* proxy_resolution_service = nullptr;
   ASSERT_TRUE(
       network_context->url_request_context()
@@ -2516,7 +2596,14 @@
   EXPECT_TRUE(proxy_resolution_service->quick_check_enabled_for_testing());
 
   // Explicitly enable.
-  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params = CreateContextParams();
+#if defined(OS_CHROMEOS)
+  context_params->dhcp_wpad_url_client =
+      network::MockMojoDhcpWpadUrlClient::CreateWithSelfOwnedReceiver(
+          std::string());
+#endif  // defined(OS_CHROMEOS)
+  context_params->proxy_resolver_factory =
+      MockMojoProxyResolverFactory::Create();
   context_params->pac_quick_check_enabled = true;
   network_context = CreateContextWithParams(std::move(context_params));
   proxy_resolution_service = nullptr;
@@ -2528,6 +2615,13 @@
 
   // Explicitly disable.
   context_params = CreateContextParams();
+#if defined(OS_CHROMEOS)
+  context_params->dhcp_wpad_url_client =
+      network::MockMojoDhcpWpadUrlClient::CreateWithSelfOwnedReceiver(
+          std::string());
+#endif  // defined(OS_CHROMEOS)
+  context_params->proxy_resolver_factory =
+      MockMojoProxyResolverFactory::Create();
   context_params->pac_quick_check_enabled = false;
   network_context = CreateContextWithParams(std::move(context_params));
   proxy_resolution_service = nullptr;
@@ -4691,73 +4785,6 @@
   EXPECT_EQ(0u, pac_errors.size());
 }
 
-// Test mojom::ProxyResolver that completes calls to GetProxyForUrl() with a
-// DIRECT "proxy". It additionally emits a script error on line 42 for every
-// call to GetProxyForUrl().
-class MockMojoProxyResolver : public proxy_resolver::mojom::ProxyResolver {
- public:
-  MockMojoProxyResolver() {}
-
- private:
-  // Overridden from proxy_resolver::mojom::ProxyResolver:
-  void GetProxyForUrl(
-      const GURL& url,
-      const net::NetworkIsolationKey& network_isolation_key,
-      mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverRequestClient>
-          pending_client) override {
-    // Report a Javascript error and then complete the request successfully,
-    // having chosen DIRECT connections.
-    mojo::Remote<proxy_resolver::mojom::ProxyResolverRequestClient> client(
-        std::move(pending_client));
-    client->OnError(42, "Failed: FindProxyForURL(url=" + url.spec() + ")");
-
-    net::ProxyInfo result;
-    result.UseDirect();
-
-    client->ReportResult(net::OK, result);
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(MockMojoProxyResolver);
-};
-
-// Test mojom::ProxyResolverFactory implementation that successfully completes
-// any CreateResolver() requests, and binds the request to a new
-// MockMojoProxyResolver.
-class MockMojoProxyResolverFactory
-    : public proxy_resolver::mojom::ProxyResolverFactory {
- public:
-  MockMojoProxyResolverFactory() {}
-
-  // Binds and returns a mock ProxyResolverFactory whose lifetime is bound to
-  // the message pipe.
-  static mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverFactory>
-  Create() {
-    mojo::PendingRemote<proxy_resolver::mojom::ProxyResolverFactory> remote;
-    mojo::MakeSelfOwnedReceiver(
-        std::make_unique<MockMojoProxyResolverFactory>(),
-        remote.InitWithNewPipeAndPassReceiver());
-    return remote;
-  }
-
- private:
-  void CreateResolver(
-      const std::string& pac_url,
-      mojo::PendingReceiver<proxy_resolver::mojom::ProxyResolver> receiver,
-      mojo::PendingRemote<
-          proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
-          pending_client) override {
-    // Bind |receiver| to a new MockMojoProxyResolver, and return success.
-    mojo::MakeSelfOwnedReceiver(std::make_unique<MockMojoProxyResolver>(),
-                                std::move(receiver));
-
-    mojo::Remote<proxy_resolver::mojom::ProxyResolverFactoryRequestClient>
-        client(std::move(pending_client));
-    client->ReportResult(net::OK);
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(MockMojoProxyResolverFactory);
-};
-
 // Tests that when a ProxyErrorClient is provided to NetworkContextParams, this
 // client's OnPACScriptError() method is called whenever the PAC script throws
 // an error.
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index f98b9334..f0e7202 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -27,6 +27,7 @@
 #include "components/network_session_configurator/common/network_features.h"
 #include "components/os_crypt/os_crypt.h"
 #include "mojo/core/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/scoped_message_error_crash_key.h"
 #include "net/base/logging_network_change_observer.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_change_notifier_posix.h"
@@ -181,9 +182,7 @@
 // message handling inside the Browser process is sufficient).
 void HandleBadMessage(const std::string& error) {
   LOG(WARNING) << "Mojo error in NetworkService:" << error;
-  static auto* bad_message_reason = base::debug::AllocateCrashKeyString(
-      "bad_message_reason", base::debug::CrashKeySize::Size256);
-  base::debug::SetCrashKeyString(bad_message_reason, error);
+  mojo::debug::ScopedMessageErrorCrashKey crash_key_value(error);
   base::debug::DumpWithoutCrashing();
 }
 
diff --git a/services/network/proxy_resolving_client_socket_unittest.cc b/services/network/proxy_resolving_client_socket_unittest.cc
index 74923b4..84a404f 100644
--- a/services/network/proxy_resolving_client_socket_unittest.cc
+++ b/services/network/proxy_resolving_client_socket_unittest.cc
@@ -690,7 +690,8 @@
       std::make_unique<net::ProxyConfigServiceFixed>(
           net::ProxyConfigWithAnnotation(proxy_config,
                                          TRAFFIC_ANNOTATION_FOR_TESTS)),
-      std::move(proxy_resolver_factory), nullptr);
+      std::move(proxy_resolver_factory), nullptr,
+      true /* quick_check_enabled */);
   context->set_proxy_resolution_service(&service);
   context->Init();
 
@@ -733,7 +734,8 @@
       std::make_unique<net::ProxyConfigServiceFixed>(
           net::ProxyConfigWithAnnotation(proxy_config,
                                          TRAFFIC_ANNOTATION_FOR_TESTS)),
-      std::move(proxy_resolver_factory), nullptr);
+      std::move(proxy_resolver_factory), nullptr,
+      true /* quick_check_enabled */);
   context->set_proxy_resolution_service(&service);
   context->Init();
 
@@ -766,7 +768,8 @@
       std::make_unique<net::ProxyConfigServiceFixed>(
           net::ProxyConfigWithAnnotation(proxy_config,
                                          TRAFFIC_ANNOTATION_FOR_TESTS)),
-      std::move(proxy_resolver_factory), nullptr);
+      std::move(proxy_resolver_factory), nullptr,
+      true /* quick_check_enabled */);
   context->set_proxy_resolution_service(&service);
   context->Init();
 
diff --git a/services/network/proxy_service_mojo.cc b/services/network/proxy_service_mojo.cc
index 6459806c..8769803 100644
--- a/services/network/proxy_service_mojo.cc
+++ b/services/network/proxy_service_mojo.cc
@@ -26,6 +26,7 @@
     std::unique_ptr<net::DhcpPacFileFetcher> dhcp_pac_file_fetcher,
     net::HostResolver* host_resolver,
     net::NetLog* net_log,
+    bool pac_quick_check_enabled,
     net::NetworkDelegate* network_delegate) {
   DCHECK(proxy_config_service);
   DCHECK(pac_file_fetcher);
@@ -41,7 +42,7 @@
                                   network_delegate,
                                   base::ThreadTaskRunnerHandle::Get()),
               net_log),
-          net_log));
+          net_log, pac_quick_check_enabled));
 
   // Configure fetchers to use for PAC script downloads and auto-detect.
   proxy_resolution_service->SetPacFileFetchers(
diff --git a/services/network/proxy_service_mojo.h b/services/network/proxy_service_mojo.h
index 030f78e2..e36d0af 100644
--- a/services/network/proxy_service_mojo.h
+++ b/services/network/proxy_service_mojo.h
@@ -47,6 +47,7 @@
     std::unique_ptr<net::DhcpPacFileFetcher> dhcp_pac_file_fetcher,
     net::HostResolver* host_resolver,
     net::NetLog* net_log,
+    bool pac_quick_check_enabled,
     net::NetworkDelegate* network_delegate);
 
 }  // namespace network
diff --git a/services/network/proxy_service_mojo_unittest.cc b/services/network/proxy_service_mojo_unittest.cc
index 242d0c1..64cdcb3b 100644
--- a/services/network/proxy_service_mojo_unittest.cc
+++ b/services/network/proxy_service_mojo_unittest.cc
@@ -127,7 +127,8 @@
                     TRAFFIC_ANNOTATION_FOR_TESTS)),
             base::WrapUnique(fetcher_),
             std::make_unique<net::DoNothingDhcpPacFileFetcher>(),
-            &mock_host_resolver_, &net_log_, &network_delegate_);
+            &mock_host_resolver_, &net_log_, true /* pac_quick_check_enabled */,
+            &network_delegate_);
   }
 
   base::test::TaskEnvironment task_environment_;
diff --git a/services/network/url_request_context_builder_mojo.cc b/services/network/url_request_context_builder_mojo.cc
index 986f9c49..95c48bb3 100644
--- a/services/network/url_request_context_builder_mojo.cc
+++ b/services/network/url_request_context_builder_mojo.cc
@@ -57,7 +57,8 @@
     net::URLRequestContext* url_request_context,
     net::HostResolver* host_resolver,
     net::NetworkDelegate* network_delegate,
-    net::NetLog* net_log) {
+    net::NetLog* net_log,
+    bool pac_quick_check_enabled) {
   DCHECK(url_request_context);
   DCHECK(host_resolver);
 
@@ -71,12 +72,12 @@
         std::move(mojo_proxy_resolver_factory_),
         std::move(proxy_config_service), std::move(pac_file_fetcher),
         std::move(dhcp_pac_file_fetcher), host_resolver, net_log,
-        network_delegate);
+        pac_quick_check_enabled, network_delegate);
   }
 
   return net::URLRequestContextBuilder::CreateProxyResolutionService(
       std::move(proxy_config_service), url_request_context, host_resolver,
-      network_delegate, net_log);
+      network_delegate, net_log, pac_quick_check_enabled);
 }
 
 }  // namespace network
diff --git a/services/network/url_request_context_builder_mojo.h b/services/network/url_request_context_builder_mojo.h
index 15ee45e..77cf6e37 100644
--- a/services/network/url_request_context_builder_mojo.h
+++ b/services/network/url_request_context_builder_mojo.h
@@ -59,7 +59,8 @@
       net::URLRequestContext* url_request_context,
       net::HostResolver* host_resolver,
       net::NetworkDelegate* network_delegate,
-      net::NetLog* net_log) override;
+      net::NetLog* net_log,
+      bool pac_quick_check_enabled) override;
 
   std::unique_ptr<net::DhcpPacFileFetcher> CreateDhcpPacFileFetcher(
       net::URLRequestContext* context);
diff --git a/testing/buildbot/chromium.ci.json b/testing/buildbot/chromium.ci.json
index c9af290d..d5dfc21 100644
--- a/testing/buildbot/chromium.ci.json
+++ b/testing/buildbot/chromium.ci.json
@@ -138608,6 +138608,11 @@
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "pixel_browser_tests",
+        "precommit_args": [
+          "--issue=${patch_issue}",
+          "--patchset=${patch_set}",
+          "--jobid=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
@@ -233397,6 +233402,11 @@
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "pixel_browser_tests",
+        "precommit_args": [
+          "--issue=${patch_issue}",
+          "--patchset=${patch_set}",
+          "--jobid=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 6990b21e..e7d1f88 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41670,6 +41670,11 @@
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "pixel_browser_tests",
+        "precommit_args": [
+          "--issue=${patch_issue}",
+          "--patchset=${patch_set}",
+          "--jobid=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index f012edf..d73ef5e 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -1763,6 +1763,11 @@
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "pixel_browser_tests",
+        "precommit_args": [
+          "--issue=${patch_issue}",
+          "--patchset=${patch_set}",
+          "--jobid=${buildbucket_build_id}"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
diff --git a/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter b/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
index 0673a30..6b75058 100644
--- a/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
@@ -125,5 +125,12 @@
 # crbug.com/1063991
 -org.chromium.chrome.features.start_surface.StartSurfaceLayoutTest.testIncognitoToggle_tabCount
 
+# crbug.com/1064367
+-org.chromium.chrome.features.start_surface.StartSurfaceLayoutTest.testSearchTermChip_adaptiveIcon
+-org.chromium.chrome.features.start_surface.StartSurfaceLayoutTest.testSearchTermChip_withChip
+
 # crbug.com/1064058
 -org.chromium.chrome.features.start_surface.StartSurfaceLayoutTest.testRecycling_aspectRatioPoint75
+
+# crbug.com/1064388
+-org.chromium.chrome.browser.feed.FeedNewTabPageTest.testFeedDisabledByPolicy
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 5058654..fc830699 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3221,14 +3221,11 @@
           '--test-launcher-retry-limit=0',
           '--enable-pixel-output-in-tests',
         ],
-        # Temporarily disable args so Gold will not make comments.
-        # TODO(crbug.com/1059469): Re-enable once we stabilize the system so that it won't
-        # comment on non-related changes.
-        # 'precommit_args': [
-        #   '--issue=${patch_issue}',
-        #   '--patchset=${patch_set}',
-        #   '--jobid=${buildbucket_build_id}',
-        # ],
+        'precommit_args': [
+          '--issue=${patch_issue}',
+          '--patchset=${patch_set}',
+          '--jobid=${buildbucket_build_id}',
+        ],
         'experiment_percentage': 100,
         'test': 'browser_tests',
         'mixins': ['chrome-gold-service-account'],
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 95d845e..f95d92a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2300,21 +2300,6 @@
             ]
         }
     ],
-    "FeedSendFeedback": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "InterestFeedFeedback"
-                    ]
-                }
-            ]
-        }
-    ],
     "FilterAdsOnAbusiveSites": [
         {
             "platforms": [
@@ -2948,6 +2933,21 @@
             ]
         }
     ],
+    "InterestFeedFeedback": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "InterestFeedFeedback"
+                    ]
+                }
+            ]
+        }
+    ],
     "IsolatePasswordSites": [
         {
             "platforms": [
diff --git a/third_party/blink/API_OWNERS b/third_party/blink/API_OWNERS
index b310740..cd8807a 100644
--- a/third_party/blink/API_OWNERS
+++ b/third_party/blink/API_OWNERS
@@ -9,6 +9,7 @@
 jochen@chromium.org
 mkwst@chromium.org
 rbyers@chromium.org
+rego@igalia.com
 slightlyoff@chromium.org
 tkent@chromium.org
 yoavweiss@chromium.org
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index c5bd628a..de7895e 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -124,7 +124,7 @@
   Animation::AnimationClassPriority priority;
   if (animation.IsCSSTransition())
     priority = Animation::AnimationClassPriority::kCssTransitionPriority;
-  else if (animation.IsCSSAnimation())
+  else if (animation.IsCSSAnimation() && animation.IsOwned())
     priority = Animation::AnimationClassPriority::kCssAnimationPriority;
   else
     priority = Animation::AnimationClassPriority::kDefaultPriority;
diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h
index 3b8f9c8..e35b7dd 100644
--- a/third_party/blink/renderer/core/animation/animation.h
+++ b/third_party/blink/renderer/core/animation/animation.h
@@ -119,6 +119,9 @@
 
   virtual bool IsCSSAnimation() const { return false; }
   virtual bool IsCSSTransition() const { return false; }
+  virtual Element* OwningElemnt() const { return nullptr; }
+  virtual void ClearOwningElement() {}
+  bool IsOwned() const { return !OwningElemnt(); }
 
   // Returns whether the animation is finished.
   bool Update(TimingUpdateReason);
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.cc b/third_party/blink/renderer/core/animation/css/css_animation.cc
index bb2d6b7..a6724f1 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animation.cc
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/core/animation/css/css_animation.h"
-
 #include "third_party/blink/renderer/core/animation/animation.h"
 #include "third_party/blink/renderer/core/animation/css/css_animations.h"
+#include "third_party/blink/renderer/core/animation/keyframe_effect.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 
 namespace blink {
@@ -16,7 +16,12 @@
                            const String& animation_name)
     : Animation(execution_context, timeline, content),
       animation_name_(animation_name),
-      ignore_css_play_state_(false) {}
+      ignore_css_play_state_(false) {
+  // The owning_element does not always equal to the target element of an
+  // animation. The following spec gives an example:
+  // https://drafts.csswg.org/css-animations-2/#owning-element-section
+  owning_element_ = To<KeyframeEffect>(effect())->target();
+}
 
 String CSSAnimation::playState() const {
   FlushStyles();
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.h b/third_party/blink/renderer/core/animation/css/css_animation.h
index f2d0641..4cd7962 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.h
+++ b/third_party/blink/renderer/core/animation/css/css_animation.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/animation/animation.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element.h"
 
 namespace blink {
 
@@ -22,6 +23,9 @@
 
   bool IsCSSAnimation() const final { return true; }
 
+  void ClearOwningElement() final { owning_element_ = nullptr; }
+  Element* OwningElement() const { return owning_element_; }
+
   const String& animationName() const { return animation_name_; }
 
   // Animation overrides.
@@ -48,6 +52,10 @@
   // https://drafts.csswg.org/css-animations-2/#interaction-between-animation-play-state-and-web-animations-API
   bool getIgnoreCSSPlayState() { return ignore_css_play_state_; }
   void resetIgnoreCSSPlayState() { ignore_css_play_state_ = false; }
+  void Trace(blink::Visitor* visitor) override {
+    Animation::Trace(visitor);
+    visitor->Trace(owning_element_);
+  }
 
  protected:
   AnimationEffect::EventDelegate* CreateEventDelegate(
@@ -74,6 +82,10 @@
   // When set, the web-animation API is overruling the animation-play-state
   // style.
   bool ignore_css_play_state_;
+  // The owning element of an animation refers to the element or pseudo-element
+  // whose animation-name property was applied that generated the animation
+  // The spec: https://drafts.csswg.org/css-animations-2/#owning-element-section
+  Member<Element> owning_element_;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index a6c7b117..d7d1dca 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -558,6 +558,7 @@
            cancelled_indices[i] < cancelled_indices[i + 1]);
     Animation& animation =
         *running_animations_[cancelled_indices[i]]->animation;
+    animation.ClearOwningElement();
     animation.cancel();
     animation.Update(kTimingUpdateOnDemand);
     running_animations_.EraseAt(cancelled_indices[i]);
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 9456f3e..0454095 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
@@ -1644,6 +1644,12 @@
   void LockImmediate(DisplayLockContext* context) {
     context->SetRequestedState(ESubtreeVisibility::kHidden);
   }
+  void RunStartOfLifecycleTasks() {
+    auto start_of_lifecycle_tasks =
+        GetDocument().View()->TakeStartOfLifecycleTasksForTest();
+    for (auto& task : start_of_lifecycle_tasks)
+      std::move(task).Run();
+  }
 };
 
 TEST_F(DisplayLockContextRenderingTest, FrameDocumentRemovedWhileAcquire) {
@@ -1786,12 +1792,19 @@
   auto* unrelated_element = GetDocument().getElementById("unrelated");
   auto* outer_element = GetDocument().getElementById("outer");
 
-  UpdateAllLifecyclePhasesForTest();
-  // Intersection observer notifications run as a task.
-  test::RunPendingTasks();
+  // Since visibility switch happens at the start of the next lifecycle, we
+  // should have clean layout for now.
+  EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
+  EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
+  EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
 
-  // The intersection observation will unlock inner, which will cause a dirty
-  // layout bit to be propagated.
+  RunStartOfLifecycleTasks();
+
+  // Now that the intersection observer notifications switch the visibility of
+  // the context, we expect to see dirty layout bits to be propagated.
   EXPECT_TRUE(outer_element->GetLayoutObject()->NeedsLayout());
   EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
   EXPECT_TRUE(unrelated_element->GetLayoutObject()->NeedsLayout());
@@ -1835,8 +1848,8 @@
   // Inner context should not be observing the lifecycle.
   EXPECT_FALSE(IsObservingLifecycle(inner_context));
 
-  // Run intersection observer notifications.
-  test::RunPendingTasks();
+  // Process any visibility changes.
+  RunStartOfLifecycleTasks();
 
   // Run the following checks a few times since we should be observing
   // lifecycle.
@@ -1878,8 +1891,8 @@
   EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
   EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
 
-  // Run intersection observer notifications.
-  test::RunPendingTasks();
+  // Process visibility changes.
+  RunStartOfLifecycleTasks();
 
   // We now should know we're visible and so we're not observing the lifecycle.
   EXPECT_FALSE(IsObservingLifecycle(inner_context));
@@ -1918,12 +1931,19 @@
   auto* unrelated_element = GetDocument().getElementById("unrelated");
   auto* outer_element = GetDocument().getElementById("outer");
 
-  UpdateAllLifecyclePhasesForTest();
-  // Intersection observer notifications run as a task.
-  test::RunPendingTasks();
+  // Since visibility switch happens at the start of the next lifecycle, we
+  // should have clean layout for now.
+  EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
+  EXPECT_FALSE(unrelated_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(unrelated_element->GetLayoutObject()->SelfNeedsLayout());
+  EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
+  EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
 
-  // The intersection observation will unlock inner, which will cause a dirty
-  // layout bit to be propagated.
+  RunStartOfLifecycleTasks();
+
+  // Now that the intersection observer notifications switch the visibility of
+  // the context, we expect to see dirty layout bits to be propagated.
   EXPECT_TRUE(outer_element->GetLayoutObject()->NeedsLayout());
   EXPECT_FALSE(outer_element->GetLayoutObject()->SelfNeedsLayout());
   EXPECT_TRUE(unrelated_element->GetLayoutObject()->NeedsLayout());
@@ -1967,8 +1987,8 @@
   // Inner context should not be observing the lifecycle.
   EXPECT_FALSE(IsObservingLifecycle(inner_context));
 
-  // Run intersection observer notifications.
-  test::RunPendingTasks();
+  // Process any visibility changes.
+  RunStartOfLifecycleTasks();
 
   // It shouldn't change the fact that we're layout clean.
   EXPECT_FALSE(outer_element->GetLayoutObject()->NeedsLayout());
@@ -2011,8 +2031,8 @@
   EXPECT_FALSE(inner_element->GetLayoutObject()->NeedsLayout());
   EXPECT_FALSE(inner_element->GetLayoutObject()->SelfNeedsLayout());
 
-  // Run intersection observer notifications.
-  test::RunPendingTasks();
+  // Process any visibility changes.
+  RunStartOfLifecycleTasks();
 
   // We're still invisible, and we don't know that we're not nested so we're
   // still observing the lifecycle.
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 1dc26e9..ed15c580 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8495,22 +8495,28 @@
         {Length::Percent(50.f)}, {std::numeric_limits<float>::min()}, this,
         WTF::BindRepeating(&Document::ProcessDisplayLockActivationObservation,
                            WrapWeakPersistent(this)),
-        IntersectionObserver::kPostTaskToDeliver);
+        IntersectionObserver::kDeliverDuringPostLifecycleSteps);
   }
   return *display_lock_activation_observer_;
 }
 
 void Document::ProcessDisplayLockActivationObservation(
     const HeapVector<Member<IntersectionObserverEntry>>& entries) {
+  DCHECK(View());
   for (auto& entry : entries) {
     auto* context = entry->target()->GetDisplayLockContext();
     DCHECK(context);
     if (entry->isIntersecting()) {
-      context->NotifyIsIntersectingViewport();
+      View()->EnqueueStartOfLifecycleTask(
+          WTF::Bind(&DisplayLockContext::NotifyIsIntersectingViewport,
+                    WrapWeakPersistent(context)));
     } else {
-      context->NotifyIsNotIntersectingViewport();
+      View()->EnqueueStartOfLifecycleTask(
+          WTF::Bind(&DisplayLockContext::NotifyIsNotIntersectingViewport,
+                    WrapWeakPersistent(context)));
     }
   }
+  View()->ScheduleAnimation();
 }
 
 void Document::ExecuteJavaScriptUrls() {
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 1e1f413..38b4190 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2296,6 +2296,18 @@
     });
   }
 
+  {
+    TRACE_EVENT0(
+        "blink",
+        "LocalFrameView::UpdateLifecyclePhases - start of lifecycle tasks");
+    ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
+      WTF::Vector<base::OnceClosure> tasks;
+      frame_view.start_of_lifecycle_tasks_.swap(tasks);
+      for (auto& task : tasks)
+        std::move(task).Run();
+    });
+  }
+
   // Run the lifecycle updates.
   UpdateLifecyclePhasesInternal(target_state);
 
@@ -4454,6 +4466,10 @@
   lifecycle_observers_.erase(observer);
 }
 
+void LocalFrameView::EnqueueStartOfLifecycleTask(base::OnceClosure closure) {
+  start_of_lifecycle_tasks_.push_back(std::move(closure));
+}
+
 #if DCHECK_IS_ON()
 LocalFrameView::DisallowLayoutInvalidationScope::
     DisallowLayoutInvalidationScope(LocalFrameView* view)
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 179a55a..fe91ce4 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -686,6 +686,16 @@
   void RegisterForLifecycleNotifications(LifecycleNotificationObserver*);
   void UnregisterFromLifecycleNotifications(LifecycleNotificationObserver*);
 
+  // Enqueue tasks to be run at the start of the next lifecycle. These tasks
+  // will run right after `WillStartLifecycleUpdate()` on the lifecycle
+  // notification observers.
+  void EnqueueStartOfLifecycleTask(base::OnceClosure);
+
+  // For testing way to steal the start-of-lifecycle tasks.
+  WTF::Vector<base::OnceClosure> TakeStartOfLifecycleTasksForTest() {
+    return std::move(start_of_lifecycle_tasks_);
+  }
+
  protected:
   void FrameRectsChanged(const IntRect&) override;
   void SelfVisibleChanged() override;
@@ -994,6 +1004,9 @@
   std::unique_ptr<OverlayInterstitialAdDetector>
       overlay_interstitial_ad_detector_;
 
+  // These tasks will be run at the beginning of the next lifecycle.
+  WTF::Vector<base::OnceClosure> start_of_lifecycle_tasks_;
+
 #if DCHECK_IS_ON()
   bool is_updating_descendant_dependent_flags_;
 #endif
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
index f2fcfb9..bb39e1b4 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
@@ -944,7 +944,9 @@
   }
 
   // This |layout_object| did not produce any fragments.
-  //
+  if (RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled())
+    return;
+
   // Try to find ancestors if this is a culled inline.
   layout_inline_ = ToLayoutInlineOrNull(&layout_object);
   if (!layout_inline_)
@@ -1076,12 +1078,14 @@
 }
 
 void NGInlineCursor::MoveToLastForSameLayoutObject() {
-  NGInlineCursor last;
-  while (IsNotNull()) {
-    last = *this;
+  if (!Current())
+    return;
+  NGInlineCursorPosition last;
+  do {
+    last = Current();
     MoveToNextForSameLayoutObject();
-  }
-  *this = last;
+  } while (Current());
+  MoveTo(last);
 }
 
 void NGInlineCursor::MoveToLastLogicalLeaf() {
@@ -1107,6 +1111,7 @@
 
 void NGInlineCursor::MoveToNextForSameLayoutObject() {
   if (layout_inline_) {
+    DCHECK(!RuntimeEnabledFeatures::LayoutNGFragmentItemEnabled());
     // Move to next fragment in culled inline box undef |layout_inline_|.
     do {
       MoveToNext();
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
index 1ad83a21..d745fb4 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -227,6 +227,22 @@
       info.enclosing_stacking_composited_layer;
   PaintLayer* enclosing_squashing_composited_layer =
       info.enclosing_squashing_composited_layer;
+
+  if (layer->NeedsCompositingInputsUpdate()) {
+    if (enclosing_stacking_composited_layer) {
+      enclosing_stacking_composited_layer->GetCompositedLayerMapping()
+          ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
+    }
+
+    if (enclosing_squashing_composited_layer) {
+      enclosing_squashing_composited_layer->GetCompositedLayerMapping()
+          ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
+    }
+
+    update_type = kForceUpdate;
+  }
+
+
   switch (layer->GetCompositingState()) {
     case kNotComposited:
       break;
@@ -240,17 +256,19 @@
       break;
   }
 
+  // invalidate again after the switch, in case
+  // enclosing_stacking_composited_layer or
+  // enclosing_squashing_composited_layer was previously null.
   if (layer->NeedsCompositingInputsUpdate()) {
     if (enclosing_stacking_composited_layer) {
       enclosing_stacking_composited_layer->GetCompositedLayerMapping()
           ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
     }
+
     if (enclosing_squashing_composited_layer) {
       enclosing_squashing_composited_layer->GetCompositedLayerMapping()
           ->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
     }
-
-    update_type = kForceUpdate;
   }
 
   if (style.GetPosition() == EPosition::kAbsolute) {
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index d87b0dc7..b8b50fdf 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -691,8 +691,7 @@
   // will-change:opacity to avoid raster invalidation (caused by otherwise a
   // created/deleted effect node) when we start/stop an opacity animation.
   // https://crbug.com/942681
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    reasons |= CompositingReason::kWillChangeOpacity;
+  reasons |= CompositingReason::kWillChangeOpacity;
   return reasons;
 }
 
@@ -851,8 +850,9 @@
   return false;
 }
 
-// TODO(crbug.com/900241): Remove this function and let the caller use
-// CompositingReason::kDirectReasonForEffectProperty directly.
+// TODO(crbug.com/900241): When this bug is fixed, we should let NeedsEffect()
+// use CompositingReason::kDirectReasonForEffectProperty directly instead of
+// calling this function. We should still call this function in UpdateEffect().
 static CompositingReasons CompositingReasonsForEffectProperty() {
   CompositingReasons reasons =
       CompositingReason::kDirectReasonsForEffectProperty;
@@ -864,8 +864,9 @@
   // will-change:transform to avoid raster invalidation (caused by otherwise a
   // created/deleted effect node) when we start/stop a transform animation.
   // https://crbug.com/942681
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
-    reasons |= CompositingReason::kWillChangeTransform;
+  // In CompositeAfterPaint, this also avoids decomposition of the effect when
+  // the object is forced compositing with will-change:transform.
+  reasons |= CompositingReason::kWillChangeTransform;
   return reasons;
 }
 
@@ -1173,8 +1174,9 @@
   return page->GetLinkHighlight().NeedsHighlightEffect(object);
 }
 
-// TODO(crbug.com/900241): Remove this function and let the caller use
-// CompositingReason::kDirectReasonForFilterProperty directly.
+// TODO(crbug.com/900241): When this bug is fixed, we should let NeedsFilter()
+// use CompositingReason::kDirectReasonForFilterProperty directly instead of
+// calling this function. We should still call this function in UpdateFilter().
 static CompositingReasons CompositingReasonsForFilterProperty() {
   CompositingReasons reasons =
       CompositingReason::kDirectReasonsForFilterProperty;
@@ -1186,10 +1188,10 @@
   // created for will-change:transform/opacity to avoid raster invalidation
   // (caused by otherwise a created/deleted filter node) when we start/stop a
   // transform/opacity animation. https://crbug.com/942681
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    reasons |= CompositingReason::kWillChangeTransform |
-               CompositingReason::kWillChangeOpacity;
-  }
+  // In CompositeAfterPaint, this also avoids decomposition of the filter when
+  // the object is forced compositing with will-change:transform/opacity.
+  reasons |= CompositingReason::kWillChangeTransform |
+             CompositingReason::kWillChangeOpacity;
   return reasons;
 }
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 5b6850d..8402f5b7 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -4323,6 +4323,10 @@
 }
 
 TEST_P(PaintPropertyTreeBuilderTest, CompositedUnderMultiColumn) {
+  // TODO(crbug.com/1064341): This test crashes in CompositeAfterPaint. Fix it.
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    return;
+
   SetBodyInnerHTML(R"HTML(
     <style>body { margin: 0; }</style>
     <div id='multicol' style='columns:3; column-fill:auto; column-gap: 0;
@@ -4471,6 +4475,10 @@
 }
 
 TEST_P(PaintPropertyTreeBuilderTest, CompositedMulticolFrameUnderMulticol) {
+  // TODO(crbug.com/1064341): This test crashes in CompositeAfterPaint. Fix it.
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    return;
+
   SetBodyInnerHTML(R"HTML(
     <style>body { margin: 0 }</style>
     <div style='columns: 3; column-gap: 0; column-fill: auto;
@@ -5371,16 +5379,8 @@
   GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
       DocumentUpdateReason::kTest);
 
-  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-    // TODO(crbug.com/900241): Without CompositeAfterPaint, we create effect and
-    // filter nodes when the transform node needs compositing for
-    // will-change:transform, for crbug.com/942681.
-    EXPECT_FALSE(ToLayoutBoxModelObject(target)->Layer()->SelfNeedsRepaint());
-  } else {
-    // All paint chunks contained by the new opacity effect node need to be
-    // re-painted.
-    EXPECT_TRUE(ToLayoutBoxModelObject(target)->Layer()->SelfNeedsRepaint());
-  }
+  EXPECT_TRUE(opacity_element->GetLayoutBox()->Layer()->SelfNeedsRepaint());
+  EXPECT_FALSE(ToLayoutBoxModelObject(target)->Layer()->SelfNeedsRepaint());
 }
 
 TEST_P(PaintPropertyTreeBuilderTest, SVGRootWithMask) {
@@ -5632,7 +5632,11 @@
                                    .Clip());
 }
 
-TEST_P(PaintPropertyTreeBuilderTest, CompositedLayerUnderClipUnerMulticol) {
+TEST_P(PaintPropertyTreeBuilderTest, CompositedLayerUnderClipUnderMulticol) {
+  // TODO(crbug.com/1064341): This test crashes in CompositeAfterPaint. Fix it.
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    return;
+
   SetBodyInnerHTML(R"HTML(
     <div id="multicol" style="columns: 2">
       <div id="clip" style="height: 100px; overflow: hidden">
@@ -6263,8 +6267,13 @@
               overflow_clip->UnsnappedClipRect().Rect());
     EXPECT_EQ(transform, &overflow_clip->LocalTransformSpace());
 
-    // TODO(wangxianzhu): Are the following correct?
-    EXPECT_EQ(nullptr, properties->Effect());
+    const auto* effect = properties->Effect();
+    ASSERT_NE(nullptr, effect);
+    EXPECT_EQ(&EffectPaintPropertyNode::Root(), effect->Parent());
+    EXPECT_EQ(transform, &effect->LocalTransformSpace());
+    EXPECT_EQ(clip_path_clip, effect->OutputClip());
+    EXPECT_EQ(SkBlendMode::kSrcOver, effect->BlendMode());
+
     EXPECT_EQ(nullptr, properties->Mask());
     EXPECT_EQ(nullptr, properties->ClipPath());
   } else {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index 57f26c801..4dc1211 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -419,6 +419,10 @@
   // The caller owns the |clock| which must outlive the Resource.
   static void SetClockForTesting(const base::Clock* clock);
 
+  size_t CalculateOverheadSizeForTest() const {
+    return CalculateOverheadSize();
+  }
+
  protected:
   Resource(const ResourceRequestHead&,
            ResourceType,
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_test.cc
index 599ec99..4353c202 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_test.cc
@@ -575,4 +575,11 @@
   EXPECT_TRUE(resource->StaleRevalidationRequested());
 }
 
+// This is a regression test for https://crbug.com/1062837.
+TEST(ResourceTest, DefaultOverheadSize) {
+  const KURL url("http://127.0.0.1:8000/foo.html");
+  auto* resource = MakeGarbageCollected<MockResource>(url);
+  EXPECT_EQ(resource->CalculateOverheadSizeForTest(), resource->OverheadSize());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/wtf/threading_primitives_test.cc b/third_party/blink/renderer/platform/wtf/threading_primitives_test.cc
index 6ce2b8bb5..2393e0127 100644
--- a/third_party/blink/renderer/platform/wtf/threading_primitives_test.cc
+++ b/third_party/blink/renderer/platform/wtf/threading_primitives_test.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_blocking_call_internal.h"
 #include "base/threading/thread.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index 761f7ca..d59e2037 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -54,6 +54,7 @@
 #image-rendering: pixelated doesn't work on canvas.
 fast/webgl/pixelated.html [ Failure ]
 
+compositing/masks/broken-mask.html [ Failure ]
 compositing/masks/mask-with-removed-filters.html [ Failure ]
 crbug.com/667946 compositing/overflow/scrolls-with-respect-to-nested.html [ Failure ]
 crbug.com/667946 compositing/overflow/scrolls-with-respect-to-transform.html [ Failure ]
@@ -91,11 +92,6 @@
 paint/invalidation/position/relative-positioned-movement-repaint.html [ Failure ]
 paint/invalidation/compositing/should-not-repaint-composited-descendants.html [ Failure ]
 
-# Should not decomposite effects for composited elements.
-crbug.com/765003 paint/invalidation/compositing/should-not-repaint-composited-filter.html [ Failure ]
-crbug.com/765003 paint/invalidation/compositing/should-not-repaint-composited-opacity.html [ Failure ]
-crbug.com/765003 compositing/contents-opaque/layer-opacity.html [ Failure ]
-
 # Extra layers for non-fast scrolling areas.
 compositing/overflow/textarea-scroll-touch.html [ Failure ]
 
@@ -106,16 +102,8 @@
 # Passes on bot, timeouts locally.
 virtual/threaded/fast/events/pinch/scroll-visual-viewport-send-boundary-events.html [ Pass Timeout ]
 
-# Outline paints incorrectly with columns
-crbug.com/1047358 paint/pagination/composited-paginated-outlined-box.html [ Failure ]
-
 paint/invalidation/media-audio-no-spurious-repaints.html [ Failure ]
 
-# Extra raster invalidation on start/end of animation. Caused by animation
-# element id namespaces
-crbug.com/900241 paint/invalidation/animation/opacity-animation.html [ Failure ]
-crbug.com/900241 paint/invalidation/animation/transform-animation.html [ Failure ]
-
 crbug.com/940033 virtual/threaded-prefer-compositing/fast/scrolling/wheel-scrolling-over-custom-scrollbar.html [ Pass Failure ]
 
 # Crash during PictureLayer::GetPicture() when DisplayItemList is finished twice.
@@ -127,8 +115,16 @@
 http/tests/devtools/layers/layer-scroll-rects-get.js [ Failure ]
 
 # Crash on non-contiguous effect on multiple columns
-fast/multicol/composited-layer-will-change.html [ Crash ]
-paint/clipath/change-mask-clip-path-multicol-crash.html [ Crash ]
+crbug.com/1064341 fast/multicol/composited-layer-will-change.html [ Crash ]
+crbug.com/1064341 paint/clipath/change-mask-clip-path-multicol-crash.html [ Crash ]
+crbug.com/1064341 fast/multicol/composited-layer-multiple-fragments-translated.html [ Crash ]
+crbug.com/1064341 fast/multicol/composited-layer-multiple-fragments.html [ Crash ]
+crbug.com/1064341 fast/multicol/composited-layer-nested.html [ Crash ]
+
+# Outline paints incorrectly with columns. For now this is hidden by crbug.com/1064341.
+# crbug.com/1047358 paint/pagination/composited-paginated-outlined-box.html [ Failure ]
+crbug.com/1064341 paint/pagination/composited-paginated-outlined-box.html [ Crash ]
+
 # Crash on weird clip hierarchy in multiple columns
 compositing/geometry/composited-in-columns.html [ Crash ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c8a28717..8b2bb45 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2219,6 +2219,14 @@
 crbug.com/305376 external/wpt/css/css-overflow/webkit-line-clamp-027.html [ Failure ]
 crbug.com/1007065 external/wpt/css/css-overflow/overflow-codependent-scrollbars.html [ Failure ]
 
+crbug.com/745905 external/wpt/css/css-ui/text-overflow-021.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-001.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-rtl-001.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-001.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-001.html [ Failure ]
+crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html [ Failure ]
+
 crbug.com/424365 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-024.html [ Failure ]
 crbug.com/441840 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-margin-001.html [ Failure ]
 crbug.com/441840 external/wpt/css/css-shapes/shape-outside/values/shape-outside-circle-004.html [ Failure ]
@@ -2943,12 +2951,6 @@
 crbug.com/626703 [ Linux ] external/wpt/html/cross-origin-embedder-policy/javascript.https.html [ Timeout ]
 crbug.com/626703 [ Mac ] external/wpt/html/cross-origin-embedder-policy/javascript.https.html [ Timeout ]
 crbug.com/626703 [ Win ] external/wpt/html/cross-origin-embedder-policy/javascript.https.html [ Timeout ]
-crbug.com/626703 [ Linux ] virtual/omt-worker-fetch/external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
-crbug.com/626703 [ Mac ] virtual/omt-worker-fetch/external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
-crbug.com/626703 [ Win ] virtual/omt-worker-fetch/external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
-crbug.com/626703 [ Linux ] external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
-crbug.com/626703 [ Mac ] external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
-crbug.com/626703 [ Win ] external/wpt/workers/modules/shared-worker-parse-error-failure.html [ Timeout ]
 crbug.com/626703 [ Linux ] virtual/omt-worker-fetch/external/wpt/workers/shared-worker-parse-error-failure.html [ Timeout ]
 crbug.com/626703 [ Mac ] virtual/omt-worker-fetch/external/wpt/workers/shared-worker-parse-error-failure.html [ Timeout ]
 crbug.com/626703 [ Win ] virtual/omt-worker-fetch/external/wpt/workers/shared-worker-parse-error-failure.html [ Timeout ]
@@ -3784,7 +3786,6 @@
 crbug.com/626703 external/wpt/css/css-fonts/font-variant-position.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-tables/floats/floats-wrap-bfc-006b.xht [ Failure ]
 crbug.com/626703 external/wpt/css/css-tables/floats/floats-wrap-bfc-006c.xht [ Failure ]
-crbug.com/626703 external/wpt/css/css-ui/text-overflow-021.html [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001a.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001b.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-006.xhtml [ Failure ]
@@ -5518,33 +5519,55 @@
 
 # Paint Timing failures
 crbug.com/1062984 external/wpt/paint-timing/border-image.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-background-size.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-canvas-context.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-gradient.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-invisible-scale-transition.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-invisible-text.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-opacity-descendant.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-opacity.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-overflow.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-display.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-opacity.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-text.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-visibility.html [ Pass Failure ]
+crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-svg.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-video-frame.html [ Timeout Failure Pass ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-video-poster.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/fcp-only/fcp-whitespace.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/mask-image.html [ Pass Failure ]
 crbug.com/1062984 external/wpt/paint-timing/replaced-content-image.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/border-image.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-background-size.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-canvas-context.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-gradient.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-text.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-scale-transition.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-invisible-text.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-opacity-descendant.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-opacity.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-overflow.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-display.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-opacity.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-visibility.html [ Pass Failure ]
-crbug.com/1062985 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-video-frame.html [ Pass Failure Timeout ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-text.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-visibility.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-svg.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-video-frame.html [ Pass Failure Timeout ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-video-poster.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-whitespace.html [ Pass Failure ]
 crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/mask-image.html [ Pass Failure ]
@@ -6524,8 +6547,6 @@
 
 # Sheriff 2020-02-10
 
-crbug.com/1050559 [ Linux ] external/wpt/badging/idlharness.any.html [ Pass Failure ]
-crbug.com/1050559 [ Linux ] external/wpt/badging/idlharness.any.worker.html [ Pass Failure ]
 crbug.com/1050559 [ Linux ] external/wpt/permissions-revoke/idlharness.any.html [ Pass Failure ]
 crbug.com/1050559 [ Linux ] external/wpt/shape-detection/idlharness.https.any.html [ Pass Failure ]
 crbug.com/1050559 [ Linux ] external/wpt/video-raf/idlharness.window.html [ Pass Failure ]
@@ -6670,3 +6691,6 @@
 crbug.com/1064065 virtual/threaded/external/wpt/css/css-animations/event-dispatch.tentative.html [ Pass Failure ]
 crbug.com/1064127 virtual/threaded/external/wpt/web-animations/timing-model/animations/update-playback-rate-slow.html [ Pass Failure ]
 crbug.com/1064463 external/wpt/web-animations/interfaces/Animation/style-change-events.html [ Pass Failure ]
+
+# Sheriff 2020-03-26
+crbug.com/1064839 fast/hidpi/image-srcset-svg-canvas-2x.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/compositing/transform-complex-page-expected.html b/third_party/blink/web_tests/compositing/transform-complex-page-expected.html
new file mode 100644
index 0000000..ac61e24
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/transform-complex-page-expected.html
@@ -0,0 +1,23 @@
+<!doctype HTML>
+<style>
+  #target .child::before {
+    content: "";
+    position: absolute;
+    height: 30px;
+    width: 30px;
+    will-change: transform;
+    background-color: green;
+  }
+  #target.Slider_pressed .child::before {
+    transform: translateZ(0);
+    }
+</style>
+<div class="ContextualPopup" style="opacity: 0.9">
+  <div id=target tabindex="-1" >
+    <div class=child></div>
+  </div>
+  <div style="position: relative; z-index: 0">
+    <div style=" position: absolute; z-index: -1; will-change: transform;"></div>
+  </div>
+<div style="position: absolute; height: 30px; width: 30px; left: 100px; background-color: green;">
+</div>
diff --git a/third_party/blink/web_tests/compositing/transform-complex-page.html b/third_party/blink/web_tests/compositing/transform-complex-page.html
new file mode 100644
index 0000000..4017ca56
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/transform-complex-page.html
@@ -0,0 +1,45 @@
+<!doctype HTML>
+<title>Page updates correctly after click with transtion transform and complex stacking contexts.</title>
+<style>
+  #target .child::before {
+    content: "";
+    position: absolute;
+    height: 30px;
+    width: 30px;
+    will-change: transform;
+    transition: transform ease-out 100ms;
+    background-color: green;
+  }
+  #target.Slider_pressed .child::before {
+    transform: translateZ(0);
+	}
+</style>
+<div class="ContextualPopup" style="opacity: 0.9">
+  <div id=target tabindex="-1" >
+    <div class=child></div>
+  </div>
+  <div style="position: relative; z-index: 0">
+    <div style=" position: absolute; z-index: -1; will-change: transform;"></div>
+  </div>
+<div style="position: absolute; height: 30px; width: 30px; left: 100px; background-color: green;">
+</div>
+<script>
+  if (testRunner)
+    testRunner.waitUntilDone();
+
+  onload = () => {
+    target.addEventListener("mousedown", () => {
+      target.classList.add("Slider_pressed");
+      if (testRunner) {
+        setTimeout(() => {
+          testRunner.notifyDone();
+        }, 200);
+      }
+    });
+    if (eventSender) {
+      eventSender.mouseMoveTo(10, 10);
+      eventSender.mouseDown();
+    }
+  };
+
+</script>
diff --git a/third_party/blink/web_tests/css3/flexbox/auto-height-column-with-border-and-padding.html b/third_party/blink/web_tests/css3/flexbox/auto-height-column-with-border-and-padding.html
deleted file mode 100644
index 0094439..0000000
--- a/third_party/blink/web_tests/css3/flexbox/auto-height-column-with-border-and-padding.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<link href="resources/flexbox.css" rel="stylesheet">
-Tests that auto-height column flexboxes with border and padding correctly size their height to their content.
-<div class="flexbox column" style="border: 5px solid salmon; padding: 5px; overflow: scroll">
-    <div class="flex-one-one-auto" style="min-height: 0">
-        <div style="height: 50px; background-color: pink">
-    <div>
-</div>
diff --git a/third_party/blink/web_tests/css3/flexbox/flex-column-relayout-assert.html b/third_party/blink/web_tests/css3/flexbox/flex-column-relayout-assert.html
deleted file mode 100644
index a8abfd9..0000000
--- a/third_party/blink/web_tests/css3/flexbox/flex-column-relayout-assert.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<link href="resources/flexbox.css" rel="stylesheet">
-<style>
-.flexbox {
-    background-color: green;
-}
-</style>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
-<body onload="checkLayout('.flexbox')">
-<div id=log></div>
-
-<p>You should see a green rectangle, 20px high.</p>
-
-<div class="flexbox column" data-expected-height="40">
-  <div id="child" data-expected-height="40"></div>
-</div>
-
-<script>
-document.getElementById('child').offsetHeight;
-document.getElementById('child').style.padding = "20px";
-</script>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index a6fe26e6..88e0eba 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -44601,6 +44601,18 @@
      {}
     ]
    ],
+   "css/css-flexbox/content-height-with-scrollbars.html": [
+    [
+     "css/css-flexbox/content-height-with-scrollbars.html",
+     [
+      [
+       "/css/css-flexbox/reference/content-height-with-scrollbars-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-flexbox/css-box-justify-content.html": [
     [
      "css/css-flexbox/css-box-justify-content.html",
@@ -142040,6 +142052,9 @@
    "css/css-flexbox/reference/auto-height-with-flex-ref.html": [
     []
    ],
+   "css/css-flexbox/reference/content-height-with-scrollbars-ref.html": [
+    []
+   ],
    "css/css-flexbox/reference/css-box-justify-content-ref.html": [
     []
    ],
@@ -175298,6 +175313,15 @@
    "native-file-system/script-tests/FileSystemWritableFileStream.js": [
     []
    ],
+   "native-io/META.yml": [
+    []
+   ],
+   "native-io/OWNERS": [
+    []
+   ],
+   "native-io/README.md": [
+    []
+   ],
    "navigation-timing/META.yml": [
     []
    ],
@@ -175853,21 +175877,6 @@
    "paint-timing/OWNERS": [
     []
    ],
-   "paint-timing/fcp-only/fcp-gradient-expected.txt": [
-    []
-   ],
-   "paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt": [
-    []
-   ],
-   "paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt": [
-    []
-   ],
-   "paint-timing/fcp-only/fcp-overflow-expected.txt": [
-    []
-   ],
-   "paint-timing/fcp-only/fcp-svg-expected.txt": [
-    []
-   ],
    "paint-timing/resources/circle.svg": [
     []
    ],
@@ -185510,9 +185519,6 @@
    "uievents/legacy-domevents-tests/submissions/Microsoft/support/style01.css": [
     []
    ],
-   "uievents/legacy/Event-subclasses-init-expected.txt": [
-    []
-   ],
    "uievents/order-of-events/README.md": [
     []
    ],
@@ -218465,6 +218471,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/columns-height-set-via-top-bottom.html": [
+    [
+     "css/css-flexbox/columns-height-set-via-top-bottom.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/display_flex_exist.html": [
     [
      "css/css-flexbox/display_flex_exist.html",
@@ -265414,6 +265426,12 @@
      }
     ]
    ],
+   "html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html": [
+    [
+     "html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html",
+     {}
+    ]
+   ],
    "html/semantics/forms/form-submission-0/submission-checks.window.js": [
     [
      "html/semantics/forms/form-submission-0/submission-checks.window.html",
@@ -278229,6 +278247,384 @@
      }
     ]
    ],
+   "native-io/close_async.tentative.https.any.js": [
+    [
+     "native-io/close_async.tentative.https.any.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/close_async.tentative.https.any.serviceworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/close_async.tentative.https.any.sharedworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/close_async.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/close_sync.tentative.https.any.js": [
+    [
+     "native-io/close_sync.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "Synchronous NativeIO API: close()."
+       ],
+       [
+        "global",
+        "!default,dedicatedworker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/concurrent_io_async.tentative.https.any.js": [
+    [
+     "native-io/concurrent_io_async.tentative.https.any.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/concurrent_io_async.tentative.https.any.serviceworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/concurrent_io_async.tentative.https.any.sharedworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/concurrent_io_async.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: close()."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/delete_async_basic.tentative.https.any.js": [
+    [
+     "native-io/delete_async_basic.tentative.https.any.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File deletion is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/delete_async_basic.tentative.https.any.serviceworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File deletion is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/delete_async_basic.tentative.https.any.sharedworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File deletion is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/delete_async_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File deletion is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/delete_sync_basic.tentative.https.any.js": [
+    [
+     "native-io/delete_sync_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "Synchronous NativeIO API: File deletion is reflected in listing."
+       ],
+       [
+        "global",
+        "!default,dedicatedworker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/open_getAll_async_basic.tentative.https.any.js": [
+    [
+     "native-io/open_getAll_async_basic.tentative.https.any.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File creation is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/open_getAll_async_basic.tentative.https.any.serviceworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File creation is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/open_getAll_async_basic.tentative.https.any.sharedworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File creation is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/open_getAll_async_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: File creation is reflected in listing."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/open_getAll_sync_basic.tentative.https.any.js": [
+    [
+     "native-io/open_getAll_sync_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "Synchronous NativeIO API: File creation is reflected in listing."
+       ],
+       [
+        "global",
+        "!default,dedicatedworker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/read_write_async_basic.tentative.https.any.js": [
+    [
+     "native-io/read_write_async_basic.tentative.https.any.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: Written bytes are read back."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/read_write_async_basic.tentative.https.any.serviceworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: Written bytes are read back."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/read_write_async_basic.tentative.https.any.sharedworker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: Written bytes are read back."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ],
+    [
+     "native-io/read_write_async_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "NativeIO API: Written bytes are read back."
+       ],
+       [
+        "global",
+        "window,worker"
+       ]
+      ]
+     }
+    ]
+   ],
+   "native-io/read_write_sync_basic.tentative.https.any.js": [
+    [
+     "native-io/read_write_sync_basic.tentative.https.any.worker.html",
+     {
+      "script_metadata": [
+       [
+        "title",
+        "Synchronous NativeIO API: Written bytes are read back."
+       ],
+       [
+        "global",
+        "!default,dedicatedworker"
+       ]
+      ]
+     }
+    ]
+   ],
    "navigation-timing/buffered-flag.window.js": [
     [
      "navigation-timing/buffered-flag.window.html",
@@ -290270,6 +290666,14 @@
      }
     ]
    ],
+   "pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html": [
+    [
+     "pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "pointerevents/pointerevent_lostpointercapture_is_first.html": [
     [
      "pointerevents/pointerevent_lostpointercapture_is_first.html",
@@ -290300,6 +290704,22 @@
      }
     ]
    ],
+   "pointerevents/pointerevent_pointercapture-in-custom-element.html": [
+    [
+     "pointerevents/pointerevent_pointercapture-in-custom-element.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
+   "pointerevents/pointerevent_pointercapture-in-shadow-dom.html": [
+    [
+     "pointerevents/pointerevent_pointercapture-in-shadow-dom.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "pointerevents/pointerevent_pointercapture-not-lost-in-chorded-buttons.html": [
     [
      "pointerevents/pointerevent_pointercapture-not-lost-in-chorded-buttons.html",
@@ -312386,6 +312806,12 @@
      {}
     ]
    ],
+   "svg/text/scripted/textpath-textlength-text-anchor-001.tentative.svg": [
+    [
+     "svg/text/scripted/textpath-textlength-text-anchor-001.tentative.svg",
+     {}
+    ]
+   ],
    "svg/types/elements/SVGGeometryElement-rect.svg": [
     [
      "svg/types/elements/SVGGeometryElement-rect.svg",
@@ -380988,6 +381414,14 @@
    "4c84a258658d8a0576a03e0e430677945e6eb005",
    "testharness"
   ],
+  "css/css-flexbox/columns-height-set-via-top-bottom.html": [
+   "80cdcc6974df9bb0b79bc91c2d4dc0aad881cf4d",
+   "testharness"
+  ],
+  "css/css-flexbox/content-height-with-scrollbars.html": [
+   "5a63322da7dbeb007aaace719cdc652e30338b9c",
+   "reftest"
+  ],
   "css/css-flexbox/css-box-justify-content.html": [
    "d5c7244f08dcad0b0955290804ec5959754a963d",
    "reftest"
@@ -384108,6 +384542,10 @@
    "d56fe356dcbb6ce87414a2075b5f47c515628016",
    "support"
   ],
+  "css/css-flexbox/reference/content-height-with-scrollbars-ref.html": [
+   "8a1484f6934dc3e30aae299380c82308cd1fec42",
+   "support"
+  ],
   "css/css-flexbox/reference/css-box-justify-content-ref.html": [
    "e8377473fdef6f93bdf0e1e0e78fd33f01c93e82",
    "support"
@@ -472349,11 +472787,11 @@
    "testharness"
   ],
   "html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html": [
-   "79350ef0cbb2dac43256e5deae19fe1ab4fc85d4",
+   "f841dba31bd255b17afa8a1c7129b6998294c154",
    "support"
   ],
   "html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html": [
-   "886e0dd42abb4fb00e2366327aea9c5bce696c36",
+   "594d9bebe4330f871f535f6bd309fa9d84388095",
    "reftest"
   ],
   "html/semantics/embedded-content/the-img-element/image.png": [
@@ -472984,6 +473422,10 @@
    "109d3b901a53537e9ae2fb17f84d25d4a764b2e2",
    "testharness"
   ],
+  "html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html": [
+   "6b50bf599b888b1c3273a4e311ba6af69d67e86b",
+   "testharness"
+  ],
   "html/semantics/forms/form-submission-0/resources/file-submission.py": [
    "5fc67faa880ffa9300a093aa0ef1f67c3a76eb0c",
    "support"
@@ -481761,7 +482203,7 @@
    "support"
   ],
   "interfaces/webrtc-stats.idl": [
-   "514d09f15a60446ad7a6ba78bd1288a047085af1",
+   "aaadda29b8a5318ae7bafb10947b2c9b8a108620",
    "support"
   ],
   "interfaces/webrtc-svc.idl": [
@@ -486580,6 +487022,54 @@
    "778437e2a592120c05e8872ec11ab76986861023",
    "support"
   ],
+  "native-io/META.yml": [
+   "22a45c20c9b847433b0f7d9b793d8c1a96f17336",
+   "support"
+  ],
+  "native-io/OWNERS": [
+   "a561eae602effa90f200553187f92c484743f6ff",
+   "support"
+  ],
+  "native-io/README.md": [
+   "eb1a8d268cebf1f308925cbc1fe30a2478b9fa2c",
+   "support"
+  ],
+  "native-io/close_async.tentative.https.any.js": [
+   "a2337e50dbcac793678981ebd47c504f0d4b0d18",
+   "testharness"
+  ],
+  "native-io/close_sync.tentative.https.any.js": [
+   "001fa57a2c0299e3dabeaa938d7474198acb6e3d",
+   "testharness"
+  ],
+  "native-io/concurrent_io_async.tentative.https.any.js": [
+   "22eea20c3121dce3a7a730d262d94d7a1ddf7848",
+   "testharness"
+  ],
+  "native-io/delete_async_basic.tentative.https.any.js": [
+   "1229ecf0f3db12bd32e50b8490eaeabd7069f1e6",
+   "testharness"
+  ],
+  "native-io/delete_sync_basic.tentative.https.any.js": [
+   "c9c600d6b167792096cb816b640d398117ceec90",
+   "testharness"
+  ],
+  "native-io/open_getAll_async_basic.tentative.https.any.js": [
+   "0487ef5e2f2f2908b82d78994ba2675cf9e1c2df",
+   "testharness"
+  ],
+  "native-io/open_getAll_sync_basic.tentative.https.any.js": [
+   "64fa90182a6191d1a9fb6f46906a371c351161bb",
+   "testharness"
+  ],
+  "native-io/read_write_async_basic.tentative.https.any.js": [
+   "cf2a3f56bc5db287b43c4b8c1ad81b6ed4ba8720",
+   "testharness"
+  ],
+  "native-io/read_write_sync_basic.tentative.https.any.js": [
+   "f6265aa83f5792f95455e3a5e298f7fb00c72226",
+   "testharness"
+  ],
   "navigation-timing/META.yml": [
    "bfb0e0f1f67a9c6aa2e7e921b09e85f8d970b290",
    "support"
@@ -494284,10 +494774,6 @@
    "18eb07bfdea11aa1c5eef9e77b5aeccd1b0c3332",
    "testharness"
   ],
-  "paint-timing/fcp-only/fcp-gradient-expected.txt": [
-   "7af6d68d36b1cdb25d690e445412b9f7777e080c",
-   "support"
-  ],
   "paint-timing/fcp-only/fcp-gradient.html": [
    "3a356f3dd1abf0f35c248fece3c877210162550b",
    "testharness"
@@ -494296,10 +494782,6 @@
    "76d459d0f40fcaa46e9561e76c99b4d4a17f157b",
    "testharness"
   ],
-  "paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt": [
-   "cf37721fce5cb714ca990a77e89e0c97f51a8927",
-   "support"
-  ],
   "paint-timing/fcp-only/fcp-invisible-3d-rotate.html": [
    "0b7fc325c5d200b8489d5d04ba26f86e62f35fae",
    "testharness"
@@ -494324,10 +494806,6 @@
    "83afdde195b73eb7a348255da05594b1a2bc45a1",
    "testharness"
   ],
-  "paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt": [
-   "c4f945c56bc86122796f53b95e85b5094f9a4dd5",
-   "support"
-  ],
   "paint-timing/fcp-only/fcp-out-of-bounds-translate.html": [
    "ee7975eec45267d5b29594ba5adf0df03d2a02e9",
    "testharness"
@@ -494336,10 +494814,6 @@
    "3553772d4fc6b68c9769442129723a57e07aea16",
    "testharness"
   ],
-  "paint-timing/fcp-only/fcp-overflow-expected.txt": [
-   "f68b8c6fa3120702e6a5b23900373b206cb911a2",
-   "support"
-  ],
   "paint-timing/fcp-only/fcp-overflow.html": [
    "d7cc34663b7f846055ff5a6ca1d07fc1b4677464",
    "testharness"
@@ -494364,10 +494838,6 @@
    "c903c4721851eff36135ef1eeeaffd9cc5a9c46c",
    "testharness"
   ],
-  "paint-timing/fcp-only/fcp-svg-expected.txt": [
-   "a3e0ff88878e98b79454da7f6ae954bd62f3e6fc",
-   "support"
-  ],
   "paint-timing/fcp-only/fcp-svg.html": [
    "bcd2372cfc3b7374ecba7e4ee1cd077b3e51f798",
    "testharness"
@@ -495428,6 +495898,10 @@
    "b77d9e31e2cff90a92234ff5e43962be6a93dd6f",
    "testharness"
   ],
+  "pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html": [
+   "f92daeb02e7c39fdedb60c58cd3eeb9085b7703a",
+   "testharness"
+  ],
   "pointerevents/pointerevent_lostpointercapture_is_first.html": [
    "6ce0f3e59a04db4ecf23d9fd60b57164ceb67f85",
    "testharness"
@@ -495444,6 +495918,14 @@
    "b09ddd740a6d6775806731c8e069a4674e3b12e3",
    "testharness"
   ],
+  "pointerevents/pointerevent_pointercapture-in-custom-element.html": [
+   "e8f143b309f2dbdda19c90e1aef1865ecf889f47",
+   "testharness"
+  ],
+  "pointerevents/pointerevent_pointercapture-in-shadow-dom.html": [
+   "8279665f97b0ee522b9bd5710d5ec5ad677df85a",
+   "testharness"
+  ],
   "pointerevents/pointerevent_pointercapture-not-lost-in-chorded-buttons.html": [
    "02a6af0a4b89abf1c0c51844ace15b54e91966e9",
    "testharness"
@@ -512792,6 +513274,10 @@
    "fa153250e79ab6a60bf167c659b4e2d777609a30",
    "reftest"
   ],
+  "svg/text/scripted/textpath-textlength-text-anchor-001.tentative.svg": [
+   "6c4f50fe871b0126548557976e8391c1d973b5fe",
+   "testharness"
+  ],
   "svg/text/visualtests/text-inline-size-001-visual.svg": [
    "086bab33b0030bb1d40dc17f960b7e3cbb098ae6",
    "visual"
@@ -518677,7 +519163,7 @@
    "testharness"
   ],
   "uievents/click/click_events_on_input.html": [
-   "731d13934f0d38505ae19334f0fa39a130021562",
+   "2f380eb4514bc79df891b409c9007271c7f66923",
    "testharness"
   ],
   "uievents/click/mouse-dblclick-event.html": [
@@ -519012,10 +519498,6 @@
    "961757bf3da5cb22fb389a02d2d8b9713772e637",
    "support"
   ],
-  "uievents/legacy/Event-subclasses-init-expected.txt": [
-   "7d1f2aa832795fd06e6d6bd2c08144b065deb983",
-   "support"
-  ],
   "uievents/legacy/Event-subclasses-init.html": [
    "a79a9f18adba6e329b6be8c45724417b4e337fde",
    "testharness"
diff --git a/third_party/blink/web_tests/external/wpt/badging/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/badging/idlharness.any-expected.txt
deleted file mode 100644
index 5f070bb7..0000000
--- a/third_party/blink/web_tests/external/wpt/badging/idlharness.any-expected.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Navigator: original interface defined
-PASS Partial interface Navigator: member names are unique
-PASS Partial interface mixin NavigatorID: member names are unique
-PASS Navigator includes NavigatorBadge: member names are unique
-PASS Navigator includes NavigatorID: member names are unique
-PASS Navigator includes NavigatorLanguage: member names are unique
-PASS Navigator includes NavigatorOnLine: member names are unique
-PASS Navigator includes NavigatorContentUtils: member names are unique
-PASS Navigator includes NavigatorCookies: member names are unique
-PASS Navigator includes NavigatorPlugins: member names are unique
-PASS Navigator includes NavigatorConcurrentHardware: member names are unique
-PASS WorkerNavigator includes NavigatorBadge: member names are unique
-PASS WorkerNavigator includes NavigatorID: member names are unique
-PASS WorkerNavigator includes NavigatorLanguage: member names are unique
-PASS WorkerNavigator includes NavigatorOnLine: member names are unique
-PASS WorkerNavigator includes NavigatorConcurrentHardware: member names are unique
-FAIL Navigator interface: operation setClientBadge(unsigned long long) assert_own_property: interface prototype object missing non-static operation expected property "setClientBadge" missing
-FAIL Navigator interface: operation clearClientBadge() assert_own_property: interface prototype object missing non-static operation expected property "clearClientBadge" missing
-FAIL Navigator interface: operation setAppBadge(unsigned long long) assert_own_property: interface prototype object missing non-static operation expected property "setAppBadge" missing
-FAIL Navigator interface: operation clearAppBadge() assert_own_property: interface prototype object missing non-static operation expected property "clearAppBadge" missing
-FAIL Navigator interface: navigator must inherit property "setClientBadge(unsigned long long)" with the proper type assert_inherits: property "setClientBadge" not found in prototype chain
-FAIL Navigator interface: calling setClientBadge(unsigned long long) on navigator with too few arguments must throw TypeError assert_inherits: property "setClientBadge" not found in prototype chain
-FAIL Navigator interface: navigator must inherit property "clearClientBadge()" with the proper type assert_inherits: property "clearClientBadge" not found in prototype chain
-FAIL Navigator interface: navigator must inherit property "setAppBadge(unsigned long long)" with the proper type assert_inherits: property "setAppBadge" not found in prototype chain
-FAIL Navigator interface: calling setAppBadge(unsigned long long) on navigator with too few arguments must throw TypeError assert_inherits: property "setAppBadge" not found in prototype chain
-FAIL Navigator interface: navigator must inherit property "clearAppBadge()" with the proper type assert_inherits: property "clearAppBadge" not found in prototype chain
-PASS WorkerNavigator interface: existence and properties of interface object
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/badging/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/badging/idlharness.any.worker-expected.txt
deleted file mode 100644
index a731513..0000000
--- a/third_party/blink/web_tests/external/wpt/badging/idlharness.any.worker-expected.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-This is a testharness.js-based test.
-PASS idl_test setup
-PASS idl_test validation
-PASS Partial interface Navigator: original interface defined
-PASS Partial interface Navigator: member names are unique
-PASS Partial interface mixin NavigatorID: member names are unique
-PASS Navigator includes NavigatorBadge: member names are unique
-PASS Navigator includes NavigatorID: member names are unique
-PASS Navigator includes NavigatorLanguage: member names are unique
-PASS Navigator includes NavigatorOnLine: member names are unique
-PASS Navigator includes NavigatorContentUtils: member names are unique
-PASS Navigator includes NavigatorCookies: member names are unique
-PASS Navigator includes NavigatorPlugins: member names are unique
-PASS Navigator includes NavigatorConcurrentHardware: member names are unique
-PASS WorkerNavigator includes NavigatorBadge: member names are unique
-PASS WorkerNavigator includes NavigatorID: member names are unique
-PASS WorkerNavigator includes NavigatorLanguage: member names are unique
-PASS WorkerNavigator includes NavigatorOnLine: member names are unique
-PASS WorkerNavigator includes NavigatorConcurrentHardware: member names are unique
-PASS Navigator interface: existence and properties of interface object
-FAIL WorkerNavigator interface: operation setAppBadge(unsigned long long) assert_own_property: interface prototype object missing non-static operation expected property "setAppBadge" missing
-FAIL WorkerNavigator interface: operation clearAppBadge() assert_own_property: interface prototype object missing non-static operation expected property "clearAppBadge" missing
-FAIL WorkerNavigator interface: navigator must inherit property "setAppBadge(unsigned long long)" with the proper type assert_inherits: property "setAppBadge" not found in prototype chain
-FAIL WorkerNavigator interface: calling setAppBadge(unsigned long long) on navigator with too few arguments must throw TypeError assert_inherits: property "setAppBadge" not found in prototype chain
-FAIL WorkerNavigator interface: navigator must inherit property "clearAppBadge()" with the proper type assert_inherits: property "clearAppBadge" not found in prototype chain
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/auto-height-column-with-border-and-padding.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/auto-height-column-with-border-and-padding.html
new file mode 100644
index 0000000..2151c21
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/auto-height-column-with-border-and-padding.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>CSS Flexbox: auto-height with border and padding</title>
+<link href="support/flexbox.css" rel="stylesheet">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#valdef-flex-direction-column">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#valdef-flex-direction-column-reverse">
+<link rel="match" href="reference/auto-height-column-with-border-and-padding-ref.html">
+Tests that auto-height column flexboxes with border and padding correctly size their height to their content.
+<div class="flexbox column" style="border: 5px solid salmon; padding: 5px; overflow: scroll">
+    <div class="flex-one-one-auto" style="min-height: 0">
+        <div style="height: 50px; background-color: pink">
+    <div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-column-relayout-assert.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-column-relayout-assert.html
new file mode 100644
index 0000000..b116987a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-column-relayout-assert.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>CSS Flexbox: Column height with padding</title>
+<link href="support/flexbox.css" rel="stylesheet">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#valdef-flex-direction-column">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#item-margins">
+<meta name="assert" content="This test checks that height of flex container works with padding">
+<style>
+.flexbox {
+    background-color: green;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.flexbox')">
+<div id=log></div>
+
+<p>You should see a green rectangle, 40px high.</p>
+
+<div class="flexbox column" data-expected-height="40">
+  <div id="child" data-expected-height="40"></div>
+</div>
+
+<script>
+document.getElementById('child').offsetHeight;
+document.getElementById('child').style.padding = "20px";
+</script>
diff --git a/third_party/blink/web_tests/css3/flexbox/flex-factor-less-than-one.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-factor-less-than-one.html
similarity index 90%
rename from third_party/blink/web_tests/css3/flexbox/flex-factor-less-than-one.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-factor-less-than-one.html
index 97b4f1e..320ddef 100644
--- a/third_party/blink/web_tests/css3/flexbox/flex-factor-less-than-one.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-factor-less-than-one.html
@@ -1,5 +1,9 @@
 <!DOCTYPE html>
-<link href="resources/flexbox.css" rel="stylesheet">
+<title>CSS Flexbox: flex factors less than one</title>
+<link href="support/flexbox.css" rel="stylesheet">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-grow">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#propdef-flex-shrink">
+<meta name="assert" content="flex-grow and flex-shrink factors less than 1 work">
 <style>
 
 html, body {
@@ -58,9 +62,9 @@
   writing-mode: vertical-rl;
 }
 </style>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
 <body onload="checkLayout('.flexbox');">
 <div id=log></div>
 
@@ -92,7 +96,7 @@
   <div class="child-flex-grow-0-5 basis" data-expected-width="50"></div>
   <div class="child-flex-grow-0-25 basis" data-expected-width="40"></div>
  </div>
- 
+
 <div class="flexbox container column">
   <div class="child-flex-grow-0-5 basis" data-expected-height="50"></div>
   <div class="child-flex-grow-0-25 basis" data-expected-height="40"></div>
@@ -137,7 +141,6 @@
   <div class="child-flex-shrink-0-5 basis-big" data-expected-width="50"></div>
   <div class="child-flex-shrink-0-25 basis-big" data-expected-width="75"></div>
  </div>
- 
 <div class="flexbox container column">
   <div class="child-flex-shrink-0-5 basis-big" data-expected-height="50"></div>
   <div class="child-flex-shrink-0-25 basis-big" data-expected-height="75"></div>
diff --git a/third_party/blink/web_tests/css3/flexbox/floated-flexitem.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/floated-flexitem.html
similarity index 68%
rename from third_party/blink/web_tests/css3/flexbox/floated-flexitem.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/floated-flexitem.html
index f953ecf..e205003 100644
--- a/third_party/blink/web_tests/css3/flexbox/floated-flexitem.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/floated-flexitem.html
@@ -1,5 +1,9 @@
 <!DOCTYPE html>
 <html>
+<title>CSS Flexbox: Ensure flex item proper formatting context.</title>
+<link rel="match" href="reference/floated-flexitem-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items">
+<meta name="assert" content="This test checks that the flex items of a flex container participate in their container’s flex formatting context, not in a block formatting context.">
 <style>
 #flexbox {
   background-color: lightgrey;
diff --git a/third_party/blink/web_tests/css3/flexbox/inline-flexbox-wrap-vertically-width-calculation.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html
similarity index 91%
rename from third_party/blink/web_tests/css3/flexbox/inline-flexbox-wrap-vertically-width-calculation.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html
index 69fd5035..e9010cf 100644
--- a/third_party/blink/web_tests/css3/flexbox/inline-flexbox-wrap-vertically-width-calculation.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/inline-flexbox-wrap-vertically-width-calculation.html
@@ -1,6 +1,11 @@
 <!DOCTYPE html>
 <html>
-<link href="resources/flexbox.css" rel="stylesheet">
+<title>CSS Flexbox: Inline flexbox width calculation with flex-direction: column and flex-wrap</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-direction-property">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-wrap-property">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#Computing_widths_and_margins">
+<link href="support/flexbox.css" rel="stylesheet">
+<meta name="assert" content="This test checks that width is correctly computed for flexbox with flex-direction: column and flex-wrap.">
 <style>
 .inline-flexbox {
     background-color: #aaa;
@@ -26,9 +31,9 @@
     background-color: yellow;
 }
 </style>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
 <body onload="checkLayout('.inline-flexbox');">
 <div id=log></div>
 <div class="inline-flexbox column align-content-flex-start wrap" data-expected-width="110" data-expected-height="60">
diff --git a/third_party/blink/web_tests/css3/flexbox/auto-height-column-with-border-and-padding-expected.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/reference/auto-height-column-with-border-and-padding-ref.html
similarity index 100%
rename from third_party/blink/web_tests/css3/flexbox/auto-height-column-with-border-and-padding-expected.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/reference/auto-height-column-with-border-and-padding-ref.html
diff --git a/third_party/blink/web_tests/css3/flexbox/floated-flexitem-expected.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/reference/floated-flexitem-ref.html
similarity index 100%
rename from third_party/blink/web_tests/css3/flexbox/floated-flexitem-expected.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/reference/floated-flexitem-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-001-ref.html
new file mode 100644
index 0000000..18836f32
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-001-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    font: 100px/1 Ahem;
+    white-space: pre;
+  }
+</style>
+<p>The test passes if it matches the reference.</p>
+
+<div>p pX</div>
+<div>pp p</div>
+<div>  pX</div>
+<div>pp p</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-rtl-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-rtl-001-ref.html
new file mode 100644
index 0000000..4be95f4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-rtl-001-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    font: 100px/1 Ahem;
+  }
+</style>
+<p>The test passes if it matches the reference.</p>
+
+<div>X pp</div>
+<div>ppp</div>
+<div>Xp</div>
+<div>ppp</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-001-ref.html
new file mode 100644
index 0000000..8b67117
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-001-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-lr;
+    font: 100px/1 Ahem;
+    height: 400px;
+    white-space: pre;
+  }
+</style>
+<p>The test passes if it matches the reference.</p>
+
+<div>p pX</div>
+<div>pp p</div>
+<div>  pX</div>
+<div>pp p</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html
new file mode 100644
index 0000000..fff3dec
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-lr;
+    font: 100px/1 Ahem;
+    height: 400px;
+  }
+</style>
+<p>The test passes if it matches the reference.</p>
+
+<div>X pp</div>
+<div>ppp</div>
+<div>Xp</div>
+<div>ppp</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-001.html
new file mode 100644
index 0000000..91c04fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-001.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis', the ellipsis should move to the new position.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    font: 100px/1 Ahem;
+    white-space: pre;
+    width: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollLeft = 200;
+    test2.scrollLeft = 400;
+    test3.scrollLeft = 200;
+    test4.scrollLeft = 1000;
+    document.documentElement.className = "";
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-rtl-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-rtl-001.html
new file mode 100644
index 0000000..81f88085
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-rtl-001.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling RTL</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-rtl-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis' in RTL, the ellipsis should move to the new position.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    font: 100px/1 Ahem;
+    white-space: pre;
+    width: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollLeft = -200;
+    test2.scrollLeft = -400;
+    test3.scrollLeft = -200;
+    test4.scrollLeft = -1000;
+    document.documentElement.className = "";
+  }));
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-001.html
new file mode 100644
index 0000000..f555e86
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-001.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling vertical-lr</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-vertical-lr-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis', the ellipsis should move to the new position. This should work in vertical-lr writing mode too.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-lr;
+    font: 100px/1 Ahem;
+    white-space: pre;
+    height: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollTop = 200;
+    test2.scrollTop = 400;
+    test3.scrollTop = 200;
+    test4.scrollTop = 1000;
+    document.documentElement.className = "";
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html
new file mode 100644
index 0000000..0d3e537
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-lr-rtl-001.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling vertical-lr RTL</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis', the ellipsis should move to the new position. This should work in vertical-lr writing mode and RTL too.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-lr;
+    font: 100px/1 Ahem;
+    white-space: pre;
+    height: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollTop = -200;
+    test2.scrollTop = -400;
+    test3.scrollTop = -200;
+    test4.scrollTop = -1000;
+    document.documentElement.className = "";
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-001.html
new file mode 100644
index 0000000..3c846e4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-001.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling vertical-rl</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-vertical-lr-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis', the ellipsis should move to the new position. This should work in vertical-rl writing mode too.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-rl;
+    font: 100px/1 Ahem;
+    white-space: pre;
+    height: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollTop = 200;
+    test2.scrollTop = 400;
+    test3.scrollTop = 200;
+    test4.scrollTop = 1000;
+    document.documentElement.className = "";
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html
new file mode 100644
index 0000000..c21031d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/text-overflow-scroll-vertical-rl-rtl-001.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: text-overflow: ellipsis and scrolling vertical-rl RTL</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-4/#ellipsis-scrolling">
+<link rel="match" href="reference/text-overflow-scroll-vertical-lr-rtl-001-ref.html">
+<meta name="flags" content="should">
+<meta name="assert" content="If you scroll an element with 'text-overflow: ellipsis', the ellipsis should move to the new position. This should work in vertical-rl writing mode and RTL too.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+<style>
+  div {
+    display: inline-block;
+    writing-mode: vertical-rl;
+    font: 100px/1 Ahem;
+    white-space: pre;
+    height: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it matches the reference.</p>
+
+<div id="test1">ppp pp p</div>
+<div id="test2">ppp pp p</div>
+<div id="test3"><span style="padding-inline-start: 400px;">ppp pp p</span></div>
+<div id="test4"><span style="padding-inline-start: 600px;">ppp pp p</span></div>
+
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    test1.scrollTop = -200;
+    test2.scrollTop = -400;
+    test3.scrollTop = -200;
+    test4.scrollTop = -1000;
+    document.documentElement.className = "";
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webrtc-stats.idl b/third_party/blink/web_tests/external/wpt/interfaces/webrtc-stats.idl
index 514d09f..aaadda29 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webrtc-stats.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webrtc-stats.idl
@@ -330,6 +330,9 @@
              DOMHighResTimeStamp           consentExpiredTimestamp;
              unsigned long                 packetsDiscardedOnSend;
              unsigned long long            bytesDiscardedOnSend;
+             unsigned long long            requestBytesSent;
+             unsigned long long            consentRequestBytesSent;
+             unsigned long long            responseBytesSent;
 };
 
 enum RTCStatsIceCandidatePairState {
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-gradient-expected.txt b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-gradient-expected.txt
deleted file mode 100644
index 7af6d68..0000000
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-gradient-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Gradients should not count as contentful assert_equals: First contentful paint marked too early.  expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt
deleted file mode 100644
index cf37721..0000000
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL First contentful paint fires due to 3d rotation into view. assert_equals: First contentful paint marked too early.  expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt
deleted file mode 100644
index c4f945c..0000000
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL First contentful paint fires due to transform-based intersection with document. assert_equals: First contentful paint marked too early.  expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-overflow-expected.txt b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-overflow-expected.txt
deleted file mode 100644
index f68b8c6..0000000
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-overflow-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL First contentful paint fires even when element is hidden due to overflow. assert_equals: Expected first contentful paint not found.  expected 1 but got 0
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-svg-expected.txt b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-svg-expected.txt
deleted file mode 100644
index a3e0ff8..0000000
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-svg-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL First contentful paint fires when SVG becomes contentful. assert_equals: First contentful paint marked too early.  expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/uievents/click/click_events_on_input.html b/third_party/blink/web_tests/external/wpt/uievents/click/click_events_on_input.html
index 731d139..2f380eb 100644
--- a/third_party/blink/web_tests/external/wpt/uievents/click/click_events_on_input.html
+++ b/third_party/blink/web_tests/external/wpt/uievents/click/click_events_on_input.html
@@ -37,10 +37,7 @@
         // Inject mouse click events.
         var actions = new test_driver.Actions();
         document.getElementById("other").focus();
-        var bounds = target.getBoundingClientRect();
-        actions.pointerMove(Math.floor(bounds.width / 5),
-                            Math.floor(bounds.height / 2),
-                            {origin: target})
+        actions.pointerMove(0, 0, {origin: target})
                .pointerDown({button: mouseButton})
                .pointerUp({button: mouseButton})
                .send()
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/contents-opaque/layer-opacity-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/contents-opaque/layer-opacity-expected.txt
new file mode 100644
index 0000000..5ad9af8
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/contents-opaque/layer-opacity-expected.txt
@@ -0,0 +1,29 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV class='box opaque-background translucent composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt
index 906235c9..44c9980 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited'",
-      "position": [-25, -25],
-      "bounds": [125, 125],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#000000",
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
index 34f1e05..afd2a61 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited-parent'",
-      "position": [-125, -125],
-      "bounds": [225, 225],
+      "position": [-100, -100],
+      "bounds": [200, 200],
       "backgroundColor": "#000000",
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt
index a0b193b45..bd589e34 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited'",
-      "position": [-25, -25],
-      "bounds": [125, 125],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#000000",
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/transformed-abs-position-inside-composited-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/transformed-abs-position-inside-composited-expected.png
index 8f89169..a3a253fc 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/transformed-abs-position-inside-composited-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/geometry/transformed-abs-position-inside-composited-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/lots-of-img-layers-with-opacity-expected.png
index 78354d59..62b02fa8 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/lots-of-img-layers-with-opacity-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/lots-of-img-layers-with-opacity-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/direct-image-mask-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/direct-image-mask-expected.png
deleted file mode 100644
index c409457..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/direct-image-mask-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/mask-layer-size-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/mask-layer-size-expected.txt
index ace5191..fcde388 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/mask-layer-size-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/mask-layer-size-expected.txt
@@ -9,8 +9,14 @@
     {
       "name": "LayoutNGBlockFlow DIV id='masked'",
       "bounds": [400, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#000000",
       "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='masked'",
+      "bounds": [400, 200],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/multiple-masks-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/multiple-masks-expected.png
deleted file mode 100644
index 36b5100..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/multiple-masks-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/simple-composited-mask-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/simple-composited-mask-expected.png
deleted file mode 100644
index db25724..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/masks/simple-composited-mask-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/opacity-with-mask-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/opacity-with-mask-expected.png
deleted file mode 100644
index b37d26a9..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/opacity-with-mask-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
deleted file mode 100644
index c65f603..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/reflections/reflection-opacity-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/reflections/reflection-opacity-expected.png
deleted file mode 100644
index e8eb3c1..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/reflections/reflection-opacity-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/opacity-squashed-owner-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/opacity-squashed-owner-expected.txt
index 8bb1a93f..eb2346d 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/opacity-squashed-owner-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/squashing/opacity-squashed-owner-expected.txt
@@ -9,6 +9,7 @@
     {
       "name": "LayoutNGBlockFlow DIV id='target' class='composited box opaque'",
       "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#ADD8E6",
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
index 98593a1..8a3967c 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited-layer' class='final-drop-shadow'",
-      "position": [-75, -75],
-      "bounds": [260, 260],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
index 27fc0a8..a82dd6b 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited'",
-      "position": [-75, -75],
-      "bounds": [250, 250],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-brightness-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-brightness-hw-expected.png
deleted file mode 100644
index c6ba4784..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-brightness-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-combined-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-combined-hw-expected.png
deleted file mode 100644
index cea4cd00..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-combined-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-contrast-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-contrast-hw-expected.png
deleted file mode 100644
index 62db196..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-contrast-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-grayscale-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-grayscale-hw-expected.png
deleted file mode 100644
index eb7f5585..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-grayscale-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-saturate-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-saturate-hw-expected.png
deleted file mode 100644
index 9960214..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-saturate-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-sepia-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-sepia-hw-expected.png
deleted file mode 100644
index b1d4ac2d..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/effect-sepia-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-composited-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-composited-expected.png
deleted file mode 100644
index 569109179..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-composited-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-expected.png
deleted file mode 100644
index a9016001..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-change-repaint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-crash-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-crash-expected.png
index a603318b6..74e4f89 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-crash-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-expected.png
index a603318b6..74e4f89 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/filters/filter-repaint-composited-fallback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/images/yuv-decode-eligible/color-profile-layer-filter-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
deleted file mode 100644
index 80c4e22..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/images/yuv-decode-eligible/color-profile-layer-filter-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/opacity-animation-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/opacity-animation-expected.txt
new file mode 100644
index 0000000..7653ffd
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/opacity-animation-expected.txt
@@ -0,0 +1,29 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='target'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/transform-animation-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/transform-animation-expected.txt
new file mode 100644
index 0000000..90e635c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/animation/transform-animation-expected.txt
@@ -0,0 +1,39 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='target'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [100, 0, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-expected.txt
index c106af1..b33cdfeb 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-expected.txt
@@ -11,6 +11,7 @@
       "bounds": [125, 125],
       "backgroundColor": "#0000FF",
       "invalidations": [
+        [0, 0, 100, 100],
         [50, 50, 75, 75]
       ],
       "transform": 1
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-individual-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-individual-expected.txt
index c106af1..b33cdfeb 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-individual-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-added-individual-expected.txt
@@ -11,6 +11,7 @@
       "bounds": [125, 125],
       "backgroundColor": "#0000FF",
       "invalidations": [
+        [0, 0, 100, 100],
         [50, 50, 75, 75]
       ],
       "transform": 1
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-expected.txt
index 0c5fa48f..0842c22 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-expected.txt
@@ -12,6 +12,7 @@
       "contentsOpaque": true,
       "backgroundColor": "#0000FF",
       "invalidations": [
+        [0, 0, 100, 100],
         [50, 50, 75, 75]
       ],
       "transform": 1
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-individual-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
index 7e338f73..1b5de403 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/containing-block-removed-individual-expected.txt
@@ -13,6 +13,7 @@
       "contentsOpaque": true,
       "backgroundColor": "#0000FF",
       "invalidations": [
+        [0, 0, 100, 100],
         [50, 50, 75, 75]
       ],
       "transform": 1
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
new file mode 100644
index 0000000..4ffed707
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
@@ -0,0 +1,29 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='composited-box'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
new file mode 100644
index 0000000..4ffed707
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
@@ -0,0 +1,29 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='composited-box'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-invalidation-with-composited-container-change-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-invalidation-with-composited-container-change-expected.txt
index 4bed6430..1130dac 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-invalidation-with-composited-container-change-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-invalidation-with-composited-container-change-expected.txt
@@ -11,11 +11,11 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='box' class='green box blurry'",
-      "position": [-30, -30],
-      "bounds": [260, 260],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "invalidations": [
-        [0, 0, 260, 260]
+        [0, 0, 200, 200]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-repaint-accelerated-on-accelerated-filter-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-repaint-accelerated-on-accelerated-filter-expected.txt
index 3359954..b70b70d 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-repaint-accelerated-on-accelerated-filter-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/filters/filter-repaint-accelerated-on-accelerated-filter-expected.txt
@@ -14,11 +14,11 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='resize' class='drop-shadow accelerated'",
-      "position": [-14, -14],
-      "bounds": [160, 260],
+      "bounds": [100, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "invalidations": [
-        [0, 0, 160, 260]
+        [0, 0, 100, 200]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/composited-thead-tfoot-repeat-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/composited-thead-tfoot-repeat-expected.png
deleted file mode 100644
index f93627a..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/composited-thead-tfoot-repeat-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/fixed-positioned-headers-and-footers-inside-transform-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/fixed-positioned-headers-and-footers-inside-transform-expected.png
deleted file mode 100644
index fe4c60fb..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/printing/fixed-positioned-headers-and-footers-inside-transform-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
index 08f1d5810..bce11d28 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-after-sw-blur-animation-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited-layer' class='final-drop-shadow'",
-      "position": [-150, -150],
-      "bounds": [520, 520],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
index 605105eb..5371d1e 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/composited-layer-bounds-with-composited-blur-expected.txt
@@ -8,8 +8,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='composited'",
-      "position": [-150, -150],
-      "bounds": [500, 500],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png
deleted file mode 100644
index 05274de..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
deleted file mode 100644
index 487badf5..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png
deleted file mode 100644
index a54bec4..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png
deleted file mode 100644
index a3a7015..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png
deleted file mode 100644
index 6598a85..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png
deleted file mode 100644
index e8c50b4..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png
deleted file mode 100644
index 27a4ba9a..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png
deleted file mode 100644
index 9c7d6c9..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-composited-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-expected.png
deleted file mode 100644
index 22560a8..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-change-repaint-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png
index e4db8c6..6ce97dc 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-crash-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png
index e4db8c6..6ce97dc 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/virtual/scalefactor200/css3/filters/filter-repaint-composited-fallback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
index 2f53b999..b6ccb2c 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
@@ -10,7 +10,6 @@
       "name": "LayoutBlockFlow DIV id='composited-box'",
       "bounds": [100, 100],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
index 2f53b999..b6ccb2c 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
@@ -10,7 +10,6 @@
       "name": "LayoutBlockFlow DIV id='composited-box'",
       "bounds": [100, 100],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
index 8c3e221..35b5125 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter-expected.txt
@@ -10,7 +10,6 @@
       "name": "LayoutNGBlockFlow DIV id='composited-box'",
       "bounds": [100, 100],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter.html b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter.html
index 14a9d4d..e21ba078 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter.html
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-filter.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <style>
 #composited-box {
-    backface-visibility: hidden;
+    will-change: opacity;
     width: 100px;
     height: 100px;
     background-color: green;
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
index 8c3e221..35b5125 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity-expected.txt
@@ -10,7 +10,6 @@
       "name": "LayoutNGBlockFlow DIV id='composited-box'",
       "bounds": [100, 100],
       "contentsOpaque": true,
-      "backfaceVisibility": "hidden",
       "backgroundColor": "#008000",
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity.html b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity.html
index 9c6cb16..66b3ad9 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity.html
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-repaint-composited-opacity.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <style>
 #composited-box {
-    backface-visibility: hidden;
+    will-change: transform;
     width: 100px;
     height: 100px;
     background: green;
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/uievents/click/click_events_on_input-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/uievents/click/click_events_on_input-expected.txt
deleted file mode 100644
index 09c2f3b8..0000000
--- a/third_party/blink/web_tests/platform/win/external/wpt/uievents/click/click_events_on_input-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Test click and auxclick on input element assert_true: Should have got at least "click". expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-026.html b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-026.html
index 810e1e1..f2d027c 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-026.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-026.html
@@ -7,6 +7,7 @@
 <meta name="assert" content="subtree-visibility visible does not add containment">
 <meta name="assert" content="subtree-visibility hidden adds containment">
 <meta name="assert" content="subtree-visibility auto adds containment">
+<meta name="assert" content="subtree-visibility auto visibility switches after rAF callbacks of the frame that produces the painted output">
 
 <style>
 .auto {
@@ -39,12 +40,25 @@
   setUp(async_container);
   async_container.classList.add("auto");
   t.step(() => assert_equals(getComputedStyle(async_container).contain, "size layout style"));
+  // Considering this as frame 1:
+  // At frame 2, the container has size-containment, and intersection
+  //   observations will be delivered at the end of that frame.
+  // At frame 3, the container still has size-containment, because visiblility
+  //   switch happens after rAF-callbacks have run.
+  // At frame 4, the container is no longer size-contained since it is visible.
   requestAnimationFrame(() => {
+    // Frame 2 checks:
+    t.step(() => assert_equals(getComputedStyle(async_container).contain, "size layout style"));
+
     requestAnimationFrame(() => {
-      // Since container is in the viewport it will not have size containment
-      // when the UA detects it's in the viewport.
-      t.step(() => assert_equals(getComputedStyle(async_container).contain, "layout style"));
-      t.done();
+      // Frame 3 checks:
+      t.step(() => assert_equals(getComputedStyle(async_container).contain, "size layout style"));
+
+      requestAnimationFrame(() => {
+        // Frame 4 checks:
+        t.step(() => assert_equals(getComputedStyle(async_container).contain, "layout style"));
+        t.done();
+      });
     });
   });
 }, "subtree-visibility: auto adds contain: size layout style;");
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index b251d5e..46afb41 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -470,6 +470,7 @@
       'Mac Builder (dbg)': 'gpu_tests_debug_bot',
       'ios-device': 'ios_release_device_compile_only',
       'ios-simulator': 'ios_error',
+      'ios-simulator-cronet': 'ios_error',
       'ios-simulator-full-configs': 'ios_error',
       'WebKit Mac10.13 (retina)': 'release_bot',
     },
@@ -884,7 +885,7 @@
       'ios-device': 'ios_release_device_compile_only',
       'ios-simulator': 'ios_error',
       'ios-simulator-full-configs': 'ios_error',
-      'ios-simulator-cronet': 'ios_cronet',
+      'ios-simulator-cronet': 'ios_error',
       'ios-simulator-code-coverage': 'clang_code_coverage_ios',
       'ios-simulator-cr-recipe': 'ios_simulator_debug_static_bot',
       'mac-osxbeta-rel': 'gpu_tests_release_trybot_deterministic_mac',
@@ -2456,7 +2457,7 @@
     },
 
     'ios_device': {
-      'mixins': ['ios'],
+      'mixins': ['ios', 'ios_use_goma_rbe' ],
       'gn_args': 'target_cpu="arm64"',
     },
 
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 81bb412..64ee9b3 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -26067,10 +26067,12 @@
 <histogram
     name="Compositing.Renderer.PercentPictureLayersWithTextButLCDTextDisabled"
     units="%" expires_after="2020-05-01">
+  <obsolete>
+    Removed in 03/2020 because changes in the data are difficult to reason about
+    as the number of layers also tends to change at the same time.
+  </obsolete>
 <!-- Name completed by histogram_suffixes name="CompositingLCDTextDisabledCountSuffixes" -->
 
-  <owner>pdr@chromium.org</owner>
-  <owner>samfort@microsoft.com</owner>
   <owner>paint-dev@chromium.org</owner>
   <summary>
     The number of PictureLayers in the active tree for each compositor frame
@@ -64672,11 +64674,21 @@
   <owner>jiwan@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
-    The number of times each assistive actions is triggered. Recorded when
+    The number of times each assistive action is triggered. Recorded when
     assistive actions are triggered.
   </summary>
 </histogram>
 
+<histogram name="InputMethod.Assistive.Success" enum="IMEAssistiveAction"
+    expires_after="2021-01-01">
+  <owner>jiwan@google.com</owner>
+  <owner>essential-inputs-team@google.com</owner>
+  <summary>
+    The number of times each assistive action is accepted. Recorded when
+    assistive actions are accepted by the users.
+  </summary>
+</histogram>
+
 <histogram name="InputMethod.AutoCorrectLevel" enum="IMECorrectionLevel"
     expires_after="2020-04-01">
   <owner>essential-inputs-team@google.com</owner>
@@ -185021,6 +185033,10 @@
 
 <histogram_suffixes name="CompositingLCDTextDisabledCountSuffixes"
     separator=".">
+  <obsolete>
+    Removed in 03/2020 because changes in the data are difficult to reason about
+    as the number of layers also tends to change at the same time.
+  </obsolete>
   <suffix name="10To30"
       label="Percentage when number of picture layers with text is 10 thru 30"/>
   <suffix name="LessThan10"
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 348ba3e..4a8b8b16 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -22,18 +22,18 @@
 components_perftests,csharrison@chromium.org,,,
 dawn_perf_tests,"enga@chromium.org, chrome-gpu-perf-owners@chromium.org",Internals>GPU>Dawn,https://dawn.googlesource.com/dawn/+/HEAD/src/tests/perf_tests/README.md,
 dromaeo,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",Blink>Bindings,,
-dummy_benchmark.noisy_benchmark_1,crouleau@chromium.org,Test>Telemetry,,
-dummy_benchmark.stable_benchmark_1,crouleau@chromium.org,Test>Telemetry,,
+dummy_benchmark.noisy_benchmark_1,"johnchen@chromium.org, wenbinzhang@google.com",Test>Telemetry,,
+dummy_benchmark.stable_benchmark_1,"johnchen@chromium.org, wenbinzhang@google.com",Test>Telemetry,,
 gpu_perftests,"reveman@chromium.org, chrome-gpu-perf-owners@chromium.org",Internals>GPU,,
 jetstream,hablich@chromium.org,Blink>JavaScript,,
 jetstream2,"hablich@chromium.org, tcwang@chromium.org",Blink>JavaScript,https://browserbench.org/JetStream/in-depth.html,
 kraken,hablich@chromium.org,Blink>JavaScript,,
-load_library_perf_tests,"xhwang@chromium.org, crouleau@chromium.org",Internals>Media>Encrypted,,
+load_library_perf_tests,"xhwang@chromium.org, jrummell@chromium.org",Internals>Media>Encrypted,,
 loading.desktop,"kouhei@chromium.org, ksakamoto@chromium.org",Blink>Loader,https://bit.ly/loading-benchmarks,"abridged,cache_temperature_cold,cache_temperature_warm,international,intl_ar_fa_he,intl_es_fr_pt_BR,intl_hi_ru,intl_ja_zh,intl_ko_th_vi,typical"
 loading.mobile,"kouhei@chromium.org, ksakamoto@chromium.org",Blink>Loader,https://bit.ly/loading-benchmarks,"abridged,cache_temperature_cold,cache_temperature_hot,cache_temperature_warm,easy_ttfmp,easy_tti,global,pwa,tough_ttfmp,tough_tti"
 media.desktop,dalecurtis@chromium.org,Internals>Media,https://chromium.googlesource.com/chromium/src/+/master/docs/speed/benchmark/harnesses/media.md,"aac,audio_only,audio_video,av1,background,beginning_to_end,busyjs,cns,h264,is_4k,is_50fps,mp3,mse,opus,seek,src,video_only,vorbis,vp8,vp9"
 media.mobile,dalecurtis@chromium.org,Internals>Media,https://chromium.googlesource.com/chromium/src/+/master/docs/speed/benchmark/harnesses/media.md,"aac,audio_only,audio_video,background,beginning_to_end,busyjs,cns,h264,mp3,mse,opus,seek,src,video_only,vorbis,vp9"
-media_perftests,"crouleau@chromium.org, dalecurtis@chromium.org",Internals>Media,,
+media_perftests,"liberato@chromium.org, dalecurtis@chromium.org",Internals>Media,,
 memory.desktop,erikchen@chromium.org,,,
 net_perftests,net-dev@chromium.org,Internals>Network,,
 octane,hablich@chromium.org,Blink>JavaScript,,
diff --git a/tools/perf/benchmarks/dummy_benchmark.py b/tools/perf/benchmarks/dummy_benchmark.py
index 038db83..b90d583 100644
--- a/tools/perf/benchmarks/dummy_benchmark.py
+++ b/tools/perf/benchmarks/dummy_benchmark.py
@@ -1,7 +1,7 @@
 # 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.
-"""Dummy benchmarks for the bisect FYI integration tests.
+"""Dummy benchmarks for the bisect FYI integration tests and other tests.
 
 The number they produce aren't meant to represent any actual performance
 data of the browser. For more information about these dummy benchmarks,
@@ -34,7 +34,9 @@
   page_set = dummy_story_set.DummyStorySet
 
 
-@benchmark.Info(emails=['crouleau@chromium.org'], component='Test>Telemetry')
+@benchmark.Info(
+    emails=['johnchen@chromium.org', 'wenbinzhang@google.com'],
+    component='Test>Telemetry')
 class DummyBenchmarkOne(_DummyBenchmark):
   """A low noise benchmark with mean=100 & std=1."""
 
@@ -46,7 +48,9 @@
     return 'dummy_benchmark.stable_benchmark_1'
 
 
-@benchmark.Info(emails=['crouleau@chromium.org'], component='Test>Telemetry')
+@benchmark.Info(
+    emails=['johnchen@chromium.org', 'wenbinzhang@google.com'],
+    component='Test>Telemetry')
 class DummyBenchmarkTwo(_DummyBenchmark):
   """A noisy benchmark with mean=50 & std=20."""
 
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index ef17159..14a756c7 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -668,12 +668,12 @@
         'eseckler@chromium.org, oysteine@chromium.org',
         'Speed>Tracing'),
     'load_library_perf_tests': BenchmarkMetadata(
-        'xhwang@chromium.org, crouleau@chromium.org',
+        'xhwang@chromium.org, jrummell@chromium.org',
         'Internals>Media>Encrypted'),
     'performance_browser_tests': BenchmarkMetadata(
         'miu@chromium.org', 'Internals>Media>ScreenCapture'),
     'media_perftests': BenchmarkMetadata(
-        'crouleau@chromium.org, dalecurtis@chromium.org',
+        'liberato@chromium.org, dalecurtis@chromium.org',
         'Internals>Media'),
     'views_perftests': BenchmarkMetadata(
         'tapted@chromium.org', 'Internals>Views'),
diff --git a/ui/aura/test/DEPS b/ui/aura/test/DEPS
index 91a1a898..a7e91f015 100644
--- a/ui/aura/test/DEPS
+++ b/ui/aura/test/DEPS
@@ -4,5 +4,5 @@
   "+mojo/core/embedder/embedder.h",
   "+services/viz/public/mojom/compositing/compositor_frame_sink.mojom.h",
   "+ui/gl",
-  "+ui/wm/core/wm_state.h",
+  "+ui/wm/core",
 ]
diff --git a/ui/aura/test/aura_test_helper.cc b/ui/aura/test/aura_test_helper.cc
index daeca12..87d6170 100644
--- a/ui/aura/test/aura_test_helper.cc
+++ b/ui/aura/test/aura_test_helper.cc
@@ -28,6 +28,7 @@
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/compositor/test/test_context_factories.h"
 #include "ui/display/screen.h"
+#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/wm_state.h"
 
 #if defined(OS_LINUX)
@@ -123,6 +124,7 @@
   host_->window()->SetEventTargeter(std::make_unique<WindowTargeter>());
 
   Window* root_window = GetContext();
+  new wm::DefaultActivationClient(root_window);  // Manages own lifetime.
   client::SetFocusClient(root_window, focus_client_.get());
   capture_client_ = std::make_unique<client::DefaultCaptureClient>(root_window);
   parenting_client_ = std::make_unique<TestWindowParentingClient>(root_window);
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index b9267812..31b6744 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -1330,7 +1330,8 @@
     observer.OnWindowAlphaShapeSet(this);
 }
 
-void Window::OnLayerFillsBoundsOpaquelyChanged() {
+void Window::OnLayerFillsBoundsOpaquelyChanged(
+    ui::PropertyChangeReason reason) {
   // Let observers know that this window's transparent status has changed.
   // Transparent status can affect the occlusion computed for windows.
   WindowOcclusionTracker::ScopedPause pause_occlusion_tracking;
@@ -1340,7 +1341,7 @@
     DCHECK(opaque_regions_for_occlusion_.empty());
 
   for (WindowObserver& observer : observers_)
-    observer.OnWindowTransparentChanged(this);
+    observer.OnWindowTransparentChanged(this, reason);
 }
 
 void Window::OnLayerTransformed(const gfx::Transform& old_transform,
diff --git a/ui/aura/window.h b/ui/aura/window.h
index aa0004d..0f625fc 100644
--- a/ui/aura/window.h
+++ b/ui/aura/window.h
@@ -601,7 +601,8 @@
                           ui::PropertyChangeReason reason) override;
   void OnLayerOpacityChanged(ui::PropertyChangeReason reason) override;
   void OnLayerAlphaShapeChanged() override;
-  void OnLayerFillsBoundsOpaquelyChanged() override;
+  void OnLayerFillsBoundsOpaquelyChanged(
+      ui::PropertyChangeReason reason) override;
 
   // Overridden from ui::EventTarget:
   bool CanAcceptEvent(const ui::Event& event) override;
diff --git a/ui/aura/window_observer.h b/ui/aura/window_observer.h
index c7872673..071c5c8 100644
--- a/ui/aura/window_observer.h
+++ b/ui/aura/window_observer.h
@@ -124,9 +124,18 @@
   // Invoked when the alpha shape of the |window|'s layer is set.
   virtual void OnWindowAlphaShapeSet(Window* window) {}
 
-  // Invoked when whether |window|'s layer fills its bounds opaquely or not
-  // is changed.
-  virtual void OnWindowTransparentChanged(Window* window) {}
+  // Invoked when whether |window|'s layer fills its bounds opaquely or not is
+  // changed.  |reason| indicates whether the value was set directly or by a
+  // color animation. Color animation happens only on LAYER_SOLID_COLOR type,
+  // and this value will always be NOT_FROM_ANIMATION on other layer types.
+  // This won't necessarily be called at every step of an animation. However, it
+  // will always be called before the first frame of the animation is rendered
+  // and when the animation ends. The client can determine whether the animation
+  // is ending by calling
+  // window->layer()->GetAnimator()->IsAnimatingProperty(
+  // ui::LayerAnimationElement::COLOR).
+  virtual void OnWindowTransparentChanged(Window* window,
+                                          ui::PropertyChangeReason reason) {}
 
   // Invoked when |window|'s position among its siblings in the stacking order
   // has changed.
diff --git a/ui/aura/window_occlusion_change_builder.cc b/ui/aura/window_occlusion_change_builder.cc
index 8411daa..b7acb5f 100644
--- a/ui/aura/window_occlusion_change_builder.cc
+++ b/ui/aura/window_occlusion_change_builder.cc
@@ -27,7 +27,6 @@
       auto it = changes_.find(window);
       if (it == changes_.end())
         continue;
-
       window->SetOcclusionInfo(it->second.occlusion_state,
                                it->second.occluded_region);
     }
diff --git a/ui/aura/window_occlusion_tracker.cc b/ui/aura/window_occlusion_tracker.cc
index cf035d5..fe5146c3 100644
--- a/ui/aura/window_occlusion_tracker.cc
+++ b/ui/aura/window_occlusion_tracker.cc
@@ -369,6 +369,10 @@
     return false;
   }
 
+  // TODO: While considering that a window whose color is animated doesn't
+  // occlude other windows helps reduce the number of times that occlusion is
+  // recomputed, it isn't necessary to consider that the window whose color is
+  // animated itself is non-occluded.
   if (WindowIsAnimated(window) || WindowIsExcluded(window)) {
     SetWindowAndDescendantsAreOccluded(window, /* is_occluded */ false,
                                        /* is_parent_visible */ true);
@@ -879,9 +883,17 @@
   });
 }
 
-void WindowOcclusionTracker::OnWindowTransparentChanged(Window* window) {
+void WindowOcclusionTracker::OnWindowTransparentChanged(
+    Window* window,
+    ui::PropertyChangeReason reason) {
+  // Call MaybeObserveAnimatedWindow() outside the lambda so that the window can
+  // be marked as animated even when its root is dirty.
+  const bool animation_started =
+      (reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
+      MaybeObserveAnimatedWindow(window);
   MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
-    return WindowOpacityChangeMayAffectOcclusionStates(window);
+    return animation_started ||
+           WindowOpacityChangeMayAffectOcclusionStates(window);
   });
 }
 
diff --git a/ui/aura/window_occlusion_tracker.h b/ui/aura/window_occlusion_tracker.h
index 7382043..c41a1343 100644
--- a/ui/aura/window_occlusion_tracker.h
+++ b/ui/aura/window_occlusion_tracker.h
@@ -331,7 +331,8 @@
   void OnWindowOpacitySet(Window* window,
                           ui::PropertyChangeReason reason) override;
   void OnWindowAlphaShapeSet(Window* window) override;
-  void OnWindowTransparentChanged(Window* window) override;
+  void OnWindowTransparentChanged(Window* window,
+                                  ui::PropertyChangeReason reason) override;
   void OnWindowTransformed(Window* window,
                            ui::PropertyChangeReason reason) override;
   void OnWindowStackingChanged(Window* window) override;
diff --git a/ui/aura/window_occlusion_tracker_unittest.cc b/ui/aura/window_occlusion_tracker_unittest.cc
index c97e5d6..b252a99 100644
--- a/ui/aura/window_occlusion_tracker_unittest.cc
+++ b/ui/aura/window_occlusion_tracker_unittest.cc
@@ -1464,9 +1464,7 @@
 // Verify that no crash occurs if an animation completes on a non-tracked
 // window's layer after the window has been removed from a root with a tracked
 // window and deleted.
-// TODO(crbug.com/1057024): Alpha in SOLID_COLOR layer doesn't work with
-// animation. Fix it and use WindowOcclusionTrackerOpacityTest.
-TEST_F(WindowOcclusionTrackerTest,
+TEST_P(WindowOcclusionTrackerOpacityTest,
        DeleteNonTrackedAnimatedWindowRemovedFromTrackedRoot) {
   ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
@@ -1486,7 +1484,8 @@
   // stops being animated.
   delegate->set_expectation(Window::OcclusionState::VISIBLE,
                             SkRegion(SkIRect::MakeXYWH(10, 0, 10, 10)));
-  Window* window = CreateUntrackedWindow(gfx::Rect(10, 0, 10, 10));
+  Window* window =
+      CreateUntrackedWindow(gfx::Rect(10, 0, 10, 10), nullptr, layer_type());
   EXPECT_FALSE(delegate->is_expecting_call());
 
   window->layer()->SetAnimator(test_controller.animator());
@@ -1497,7 +1496,9 @@
   // of its LayerAnimator (after |observer|). Upon beginning animation, the
   // window should no longer affect the occluded region.
   delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
-  window->layer()->SetOpacity(0.5f);
+  SetOpacity(window, 0.1);
+  // Drive the animation so that the color's alpha value changes.
+  test_controller.Step(kTransitionDuration / 2);
   EXPECT_FALSE(delegate->is_expecting_call());
 
   // Remove the non-tracked window from its root. WindowOcclusionTracker should
@@ -1512,6 +1513,50 @@
   window->layer()->GetAnimator()->StopAnimating();
 }
 
+TEST_P(WindowOcclusionTrackerOpacityTest,
+       OpacityAnimationShouldNotOccludeWindow) {
+  ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+  ui::LayerAnimatorTestController test_controller(
+      ui::LayerAnimator::CreateImplicitAnimator());
+  ui::ScopedLayerAnimationSettings layer_animation_settings(
+      test_controller.animator());
+  layer_animation_settings.SetTransitionDuration(kTransitionDuration);
+
+  // Create a tracked window. Expect it to be non-occluded.
+  MockWindowDelegate* delegate = new MockWindowDelegate();
+  delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+  auto* foo = CreateTrackedWindow(delegate, gfx::Rect(0, 0, 10, 10));
+  foo->SetName("A");
+  EXPECT_FALSE(delegate->is_expecting_call());
+
+  // Create a non-tracked window which occludes the tracked window.
+  delegate->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+  Window* window =
+      CreateUntrackedWindow(gfx::Rect(0, 0, 10, 10), nullptr, layer_type());
+  window->SetName("B");
+  EXPECT_FALSE(delegate->is_expecting_call());
+
+  // Set the opacity to make the tracked window VISIBLE.
+  delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+  SetOpacity(window, 0.1);
+  EXPECT_FALSE(delegate->is_expecting_call());
+
+  // Animate the window. WindowOcclusionTracker should add itself as an observer
+  // of its LayerAnimator (after |observer|). Upon beginning animation, the
+  // window should no longer affect the occluded region.
+  window->layer()->SetAnimator(test_controller.animator());
+  delegate->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+  SetOpacity(window, 1.0);
+  EXPECT_TRUE(delegate->is_expecting_call());
+  // Drive the animation so that the color's alpha value changes.
+  test_controller.Step(kTransitionDuration / 2);
+  EXPECT_TRUE(delegate->is_expecting_call());
+  window->layer()->GetAnimator()->StopAnimating();
+  EXPECT_FALSE(delegate->is_expecting_call());
+  window->layer()->SetAnimator(nullptr);
+}
+
 namespace {
 
 class WindowDelegateHidingWindowIfOccluded : public MockWindowDelegate {
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index 297469f..0c266ac8 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -62,6 +62,7 @@
     "ime_candidate_window_handler_interface.h",
     "ime_engine_handler_interface.h",
     "ime_input_context_handler_interface.h",
+    "ime_suggestion_window_handler_interface.h",
     "input_method.h",
     "input_method_base.cc",
     "input_method_base.h",
diff --git a/ui/base/ime/chromeos/input_method_chromeos.cc b/ui/base/ime/chromeos/input_method_chromeos.cc
index 707aa88..a11ec20 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos.cc
@@ -209,7 +209,9 @@
 
   chromeos::IMECandidateWindowHandlerInterface* candidate_window =
       ui::IMEBridge::Get()->GetCandidateWindowHandler();
-  if (!candidate_window)
+  chromeos::IMESuggestionWindowHandlerInterface* suggestion_window =
+      ui::IMEBridge::Get()->GetSuggestionWindowHandler();
+  if (!candidate_window && !suggestion_window)
     return;
 
   const gfx::Rect caret_rect = client->GetCaretBounds();
@@ -222,8 +224,10 @@
   // avoid a bad user experience (the IME window moved to upper left corner).
   if (composition_head.IsEmpty())
     composition_head = caret_rect;
-  candidate_window->SetCursorBounds(caret_rect, composition_head);
-
+  if (candidate_window)
+    candidate_window->SetCursorBounds(caret_rect, composition_head);
+  if (suggestion_window)
+    suggestion_window->SetBounds(caret_rect);
   gfx::Range text_range;
   gfx::Range selection_range;
   base::string16 surrounding_text;
@@ -384,6 +388,12 @@
   if (candidate_window)
     candidate_window->FocusStateChanged(IsNonPasswordInputFieldFocused());
 
+  // Propagate focus event to suggestion window handler.
+  chromeos::IMESuggestionWindowHandlerInterface* suggestion_window =
+      ui::IMEBridge::Get()->GetSuggestionWindowHandler();
+  if (suggestion_window)
+    suggestion_window->FocusStateChanged();
+
   ui::IMEEngineHandlerInterface::InputContext context(
       GetTextInputType(), GetTextInputMode(), GetTextInputFlags(),
       GetClientFocusReason(), GetClientShouldDoLearning());
diff --git a/ui/base/ime/ime_bridge.cc b/ui/base/ime/ime_bridge.cc
index 5db2a8e4..ff81081 100644
--- a/ui/base/ime/ime_bridge.cc
+++ b/ui/base/ime/ime_bridge.cc
@@ -91,6 +91,18 @@
       const override {
     return candidate_window_handler_;
   }
+
+  // IMEBridge override.
+  void SetSuggestionWindowHandler(
+      chromeos::IMESuggestionWindowHandlerInterface* handler) override {
+    suggestion_window_handler_ = handler;
+  }
+
+  // IMEBridge override.
+  chromeos::IMESuggestionWindowHandlerInterface* GetSuggestionWindowHandler()
+      const override {
+    return suggestion_window_handler_;
+  }
 #endif
 
  private:
@@ -102,6 +114,8 @@
 #if defined(OS_CHROMEOS)
   chromeos::IMECandidateWindowHandlerInterface* candidate_window_handler_ =
       nullptr;
+  chromeos::IMESuggestionWindowHandlerInterface* suggestion_window_handler_ =
+      nullptr;
 #endif
 
   DISALLOW_COPY_AND_ASSIGN(IMEBridgeImpl);
diff --git a/ui/base/ime/ime_bridge.h b/ui/base/ime/ime_bridge.h
index 846bbba..fe4b0d16 100644
--- a/ui/base/ime/ime_bridge.h
+++ b/ui/base/ime/ime_bridge.h
@@ -14,9 +14,11 @@
 
 #if defined(OS_CHROMEOS)
 #include "ui/base/ime/ime_candidate_window_handler_interface.h"
+#include "ui/base/ime/ime_suggestion_window_handler_interface.h"
 
 namespace chromeos {
 class IMECandidateWindowHandlerInterface;
+class IMESuggestionWindowHandlerInterface;
 }
 #endif
 
@@ -81,6 +83,11 @@
   // window service, pass NULL for |handler|. Caller must release |handler|.
   virtual void SetCandidateWindowHandler(
       chromeos::IMECandidateWindowHandlerInterface* handler) = 0;
+
+  virtual chromeos::IMESuggestionWindowHandlerInterface*
+  GetSuggestionWindowHandler() const = 0;
+  virtual void SetSuggestionWindowHandler(
+      chromeos::IMESuggestionWindowHandlerInterface* handler) = 0;
 #endif
 
  protected:
diff --git a/ui/base/ime/ime_suggestion_window_handler_interface.h b/ui/base/ime/ime_suggestion_window_handler_interface.h
new file mode 100644
index 0000000..ab0f6b5
--- /dev/null
+++ b/ui/base/ime/ime_suggestion_window_handler_interface.h
@@ -0,0 +1,43 @@
+// Copyright 2020 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 UI_BASE_IME_IME_SUGGESTION_WINDOW_HANDLER_INTERFACE_H_
+#define UI_BASE_IME_IME_SUGGESTION_WINDOW_HANDLER_INTERFACE_H_
+
+#include <stdint.h>
+
+#include "base/component_export.h"
+#include "base/strings/string16.h"
+
+namespace gfx {
+class Rect;
+}  // namespace gfx
+
+namespace chromeos {
+
+// A interface to handle the suggestion window related method call.
+class COMPONENT_EXPORT(UI_BASE_IME) IMESuggestionWindowHandlerInterface {
+ public:
+  virtual ~IMESuggestionWindowHandlerInterface() {}
+
+  // Called when showing/hiding suggestion window.
+  virtual void Show(const base::string16& text) {}
+  virtual void Hide() {}
+
+  // Called to get the current suggestion text.
+  virtual base::string16 GetText() const = 0;
+
+  // Called when the application changes its caret bounds.
+  virtual void SetBounds(const gfx::Rect& cursor_bounds) = 0;
+
+  // Called when the text field's focus state is changed.
+  virtual void FocusStateChanged() {}
+
+ protected:
+  IMESuggestionWindowHandlerInterface() {}
+};
+
+}  // namespace chromeos
+
+#endif  // UI_BASE_IME_IME_SUGGESTION_WINDOW_HANDLER_INTERFACE_H_
diff --git a/ui/chromeos/BUILD.gn b/ui/chromeos/BUILD.gn
index 8a5c1a3..8f55d2d 100644
--- a/ui/chromeos/BUILD.gn
+++ b/ui/chromeos/BUILD.gn
@@ -22,6 +22,10 @@
     "ime/input_method_menu_item.h",
     "ime/input_method_menu_manager.cc",
     "ime/input_method_menu_manager.h",
+    "ime/suggestion_view.cc",
+    "ime/suggestion_view.h",
+    "ime/suggestion_window_view.cc",
+    "ime/suggestion_window_view.h",
     "user_activity_power_manager_notifier.cc",
     "user_activity_power_manager_notifier.h",
   ]
diff --git a/ui/chromeos/ime/suggestion_view.cc b/ui/chromeos/ime/suggestion_view.cc
new file mode 100644
index 0000000..8e97d93
--- /dev/null
+++ b/ui/chromeos/ime/suggestion_view.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 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 "ui/chromeos/ime/suggestion_view.h"
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/widget/widget.h"
+
+namespace ui {
+namespace ime {
+
+namespace {
+
+// Creates the suggestion label, and returns it (never returns nullptr).
+// The label text is not set in this function.
+views::Label* CreateSuggestionLabel() {
+  // Create the suggestion label. The label will be added to |this| as a
+  // child view, hence it's deleted when |this| is deleted.
+  views::Label* suggestion_label = new views::Label;
+
+  suggestion_label->SetFontList(kSuggestionFont);
+  suggestion_label->SetEnabledColor(kSuggestionLabelColor);
+  suggestion_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  suggestion_label->SetBorder(
+      views::CreateEmptyBorder(gfx::Insets(kPadding / 2, 0)));
+
+  return suggestion_label;
+}
+
+// Creates the "tab" annotation label, and return it (never returns nullptr).
+views::Label* CreateAnnotationLabel() {
+  views::Label* annotation_label = new views::Label;
+  annotation_label->SetFontList(kAnnotationFont);
+  annotation_label->SetEnabledColor(kSuggestionLabelColor);
+  annotation_label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+
+  // Set insets.
+  const gfx::Insets insets(0, 0, 0, kPadding / 2);
+  annotation_label->SetBorder(views::CreateRoundedRectBorder(
+      kAnnotationBorderThickness, kAnnotationCornerRadius, insets,
+      kSuggestionLabelColor));
+
+  // Set text.
+  annotation_label->SetText(base::UTF8ToUTF16(kTabKey));
+
+  return annotation_label;
+}
+
+}  // namespace
+
+SuggestionView::SuggestionView()
+    : suggestion_label_(nullptr),
+      annotation_label_(nullptr),
+      suggestion_width_(0) {
+  suggestion_label_ = CreateSuggestionLabel();
+  annotation_label_ = CreateAnnotationLabel();
+
+  AddChildView(suggestion_label_);
+  AddChildView(annotation_label_);
+}
+
+void SuggestionView::SetText(const base::string16& text) {
+  suggestion_label_->SetText(text);
+  suggestion_width_ = suggestion_label_->GetPreferredSize().width();
+}
+
+const char* SuggestionView::GetClassName() const {
+  return "SuggestionView";
+}
+
+void SuggestionView::Layout() {
+  suggestion_label_->SetBounds(kPadding, 0, suggestion_width_, height());
+
+  int annotation_left = kPadding + suggestion_width_ + kPadding;
+  int right = bounds().right();
+  annotation_label_->SetBounds(annotation_left, kAnnotationPaddingHeight,
+                               right - annotation_left - kPadding / 2,
+                               height() - 2 * kAnnotationPaddingHeight);
+}
+
+gfx::Size SuggestionView::CalculatePreferredSize() const {
+  gfx::Size size;
+
+  gfx::Size suggestion_size = suggestion_label_->GetPreferredSize();
+  suggestion_size.SetToMax(gfx::Size(suggestion_width_, 0));
+  size.Enlarge(suggestion_size.width() + 2 * kPadding, 0);
+  size.SetToMax(suggestion_size);
+  gfx::Size annotation_size = annotation_label_->GetPreferredSize();
+  size.Enlarge(annotation_size.width() + kPadding, 0);
+  // size.SetToMax(annotation_size);
+  LOG(ERROR) << "suggestion_size " << suggestion_size.width()
+             << " annotation size " << annotation_size.width() << " size "
+             << size.width() << " height " << size.height();
+  return size;
+}
+
+}  // namespace ime
+}  // namespace ui
diff --git a/ui/chromeos/ime/suggestion_view.h b/ui/chromeos/ime/suggestion_view.h
new file mode 100644
index 0000000..8b80230
--- /dev/null
+++ b/ui/chromeos/ime/suggestion_view.h
@@ -0,0 +1,71 @@
+// Copyright 2020 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 UI_CHROMEOS_IME_SUGGESTION_VIEW_H_
+#define UI_CHROMEOS_IME_SUGGESTION_VIEW_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "ui/chromeos/ui_chromeos_export.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/view.h"
+
+namespace ui {
+namespace ime {
+// Font-related constants
+constexpr char kFontStyle[] = "Roboto";
+constexpr int kSuggestionFontSize = 14;
+constexpr int kAnnotationFontSize = 10;
+const gfx::FontList kSuggestionFont({kFontStyle},
+                                    gfx::Font::NORMAL,
+                                    kSuggestionFontSize,
+                                    gfx::Font::Weight::NORMAL);
+const gfx::FontList kAnnotationFont({kFontStyle},
+                                    gfx::Font::NORMAL,
+                                    kAnnotationFontSize,
+                                    gfx::Font::Weight::NORMAL);
+
+// Style-related constants
+constexpr int kAnnotationBorderThickness = 1;
+constexpr int kAnnotationCornerRadius = 4;
+constexpr int kPadding = 10;
+constexpr int kAnnotationPaddingHeight = 6;
+constexpr char kTabKey[] = "tab";
+constexpr SkColor kSuggestionLabelColor =
+    SkColorSetA(gfx::kGoogleGrey900, gfx::kGoogleGreyAlpha500);
+
+// SuggestionView renders a suggestion.
+class UI_CHROMEOS_EXPORT SuggestionView : public views::View {
+ public:
+  SuggestionView();
+  ~SuggestionView() override {}
+
+  void SetText(const base::string16& text);
+
+ private:
+  friend class SuggestionWindowViewTest;
+  FRIEND_TEST_ALL_PREFIXES(SuggestionWindowViewTest, ShortcutSettingTest);
+
+  // Overridden from View:
+  const char* GetClassName() const override;
+  void Layout() override;
+  gfx::Size CalculatePreferredSize() const override;
+
+  // Views created in the class will be part of tree of |this|, so these
+  // child views will be deleted when |this| is deleted.
+
+  // The suggestion label renders suggestions.
+  views::Label* suggestion_label_;
+  // The annotation label renders annotations.
+  views::Label* annotation_label_;
+
+  int suggestion_width_;
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestionView);
+};
+
+}  // namespace ime
+}  // namespace ui
+
+#endif  // UI_CHROMEOS_IME_SUGGESTION_VIEW_H_
diff --git a/ui/chromeos/ime/suggestion_window_view.cc b/ui/chromeos/ime/suggestion_window_view.cc
new file mode 100644
index 0000000..c33f105
--- /dev/null
+++ b/ui/chromeos/ime/suggestion_window_view.cc
@@ -0,0 +1,117 @@
+// Copyright 2020 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 "ui/chromeos/ime/suggestion_window_view.h"
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/chromeos/ime/suggestion_view.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/display/types/display_constants.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/bubble/bubble_border.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/wm/core/window_animations.h"
+
+namespace ui {
+namespace ime {
+
+namespace {
+constexpr int kSuggestionWindowCornerRadius = 5;
+
+class SuggestionWindowBorder : public views::BubbleBorder {
+ public:
+  SuggestionWindowBorder()
+      : views::BubbleBorder(views::BubbleBorder::NONE,
+                            views::BubbleBorder::SMALL_SHADOW,
+                            SK_ColorTRANSPARENT),
+        offset_(0) {
+    SetCornerRadius(kSuggestionWindowCornerRadius);
+    set_use_theme_background_color(true);
+  }
+  ~SuggestionWindowBorder() override {}
+
+  void set_offset(int offset) { offset_ = offset; }
+
+ private:
+  int offset_;
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestionWindowBorder);
+};
+
+}  // namespace
+
+SuggestionWindowView::SuggestionWindowView(gfx::NativeView parent,
+                                           int window_shell_id)
+    : window_shell_id_(window_shell_id) {
+  DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
+  SetCanActivate(false);
+  DCHECK(parent);
+  set_parent_window(parent);
+  set_margins(gfx::Insets());
+
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical));
+
+  suggestion_view_ = new SuggestionView();
+  AddChildView(suggestion_view_);
+}
+
+SuggestionWindowView::~SuggestionWindowView() {}
+
+views::Widget* SuggestionWindowView::InitWidget() {
+  views::Widget* widget = BubbleDialogDelegateView::CreateBubble(this);
+
+  wm::SetWindowVisibilityAnimationTransition(widget->GetNativeView(),
+                                             wm::ANIMATE_NONE);
+
+  GetBubbleFrameView()->SetBubbleBorder(
+      std::make_unique<SuggestionWindowBorder>());
+  GetBubbleFrameView()->OnThemeChanged();
+  return widget;
+}
+
+void SuggestionWindowView::Hide() {
+  GetWidget()->Close();
+}
+
+void SuggestionWindowView::Show(const base::string16& text) {
+  UpdateSuggestion(text);
+  suggestion_view_->SetVisible(true);
+  SizeToContents();
+}
+
+void SuggestionWindowView::UpdateSuggestion(const base::string16& text) {
+  suggestion_view_->SetText(text);
+
+  std::unique_ptr<SuggestionWindowBorder> border =
+      std::make_unique<SuggestionWindowBorder>();
+
+  GetBubbleFrameView()->SetBubbleBorder(std::move(border));
+  GetBubbleFrameView()->OnThemeChanged();
+}
+
+void SuggestionWindowView::SetBounds(const gfx::Rect& cursor_bounds) {
+  SetAnchorRect(cursor_bounds);
+}
+
+const char* SuggestionWindowView::GetClassName() const {
+  return "SuggestionWindowView";
+}
+
+}  // namespace ime
+}  // namespace ui
diff --git a/ui/chromeos/ime/suggestion_window_view.h b/ui/chromeos/ime/suggestion_window_view.h
new file mode 100644
index 0000000..348c4ded
--- /dev/null
+++ b/ui/chromeos/ime/suggestion_window_view.h
@@ -0,0 +1,57 @@
+// Copyright 2020 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 UI_CHROMEOS_IME_SUGGESTION_WINDOW_VIEW_H_
+#define UI_CHROMEOS_IME_SUGGESTION_WINDOW_VIEW_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/chromeos/ui_chromeos_export.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+
+namespace ui {
+namespace ime {
+
+class SuggestionView;
+
+// SuggestionWindowView is the main container of the suggestion window UI.
+class UI_CHROMEOS_EXPORT SuggestionWindowView
+    : public views::BubbleDialogDelegateView {
+ public:
+  explicit SuggestionWindowView(
+      gfx::NativeView parent,
+      int window_shell_id =
+          -1 /* equals ash::ShellWindowId::kShellWindowId_Invalid */);
+  ~SuggestionWindowView() override;
+  views::Widget* InitWidget();
+
+  // Hides.
+  void Hide();
+
+  // Shows suggestion text.
+  void Show(const base::string16& text);
+  void UpdateSuggestion(const base::string16& text);
+
+  void SetBounds(const gfx::Rect& cursor_bounds);
+
+ private:
+  friend class SuggestionWindowViewTest;
+
+  // views::BubbleDialogDelegateView:
+  const char* GetClassName() const override;
+
+  // The suggestion view is used for rendering suggestion.
+  SuggestionView* suggestion_view_;
+
+  // Corresponds to ash::ShellWindowId.
+  const int window_shell_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestionWindowView);
+};
+
+}  // namespace ime
+}  // namespace ui
+
+#endif  // UI_CHROMEOS_IME_SUGGESTION_WINDOW_VIEW_H_
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 73ce83d8..3e476180 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -712,15 +712,8 @@
 }
 
 void Layer::SetFillsBoundsOpaquely(bool fills_bounds_opaquely) {
-  if (fills_bounds_opaquely_ == fills_bounds_opaquely)
-    return;
-
-  fills_bounds_opaquely_ = fills_bounds_opaquely;
-
-  cc_layer_->SetContentsOpaque(fills_bounds_opaquely);
-
-  if (delegate_)
-    delegate_->OnLayerFillsBoundsOpaquelyChanged();
+  SetFillsBoundsOpaquelyWithReason(fills_bounds_opaquely,
+                                   PropertyChangeReason::NOT_FROM_ANIMATION);
 }
 
 void Layer::SetFillsBoundsCompletely(bool fills_bounds_completely) {
@@ -1446,7 +1439,7 @@
   DCHECK_EQ(type_, LAYER_SOLID_COLOR);
   cc_layer_->SetBackgroundColor(color);
   cc_layer_->SetSafeOpaqueBackgroundColor(color);
-  SetFillsBoundsOpaquely(SkColorGetA(color) == 0xFF);
+  SetFillsBoundsOpaquelyWithReason(SkColorGetA(color) == 0xFF, reason);
 }
 
 void Layer::SetClipRectFromAnimation(const gfx::Rect& clip_rect,
@@ -1656,4 +1649,17 @@
     child->GetFlattenedWeakList(flattened_list);
 }
 
+void Layer::SetFillsBoundsOpaquelyWithReason(bool fills_bounds_opaquely,
+                                             PropertyChangeReason reason) {
+  if (fills_bounds_opaquely_ == fills_bounds_opaquely)
+    return;
+
+  fills_bounds_opaquely_ = fills_bounds_opaquely;
+
+  cc_layer_->SetContentsOpaque(fills_bounds_opaquely);
+
+  if (delegate_)
+    delegate_->OnLayerFillsBoundsOpaquelyChanged(reason);
+}
+
 }  // namespace ui
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 95efd1e..505c81e6 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -598,6 +598,10 @@
   // rooted from |this|.
   void GetFlattenedWeakList(std::vector<base::WeakPtr<Layer>>* flattened_list);
 
+  // Same as SetFillsBoundsOpaque but with a reason how it's changed.
+  void SetFillsBoundsOpaquelyWithReason(bool fills_bounds_opaquely,
+                                        PropertyChangeReason reason);
+
   const LayerType type_;
 
   Compositor* compositor_;
diff --git a/ui/compositor/layer_delegate.cc b/ui/compositor/layer_delegate.cc
index 8973aac..3ed8301 100644
--- a/ui/compositor/layer_delegate.cc
+++ b/ui/compositor/layer_delegate.cc
@@ -16,7 +16,8 @@
 
 void LayerDelegate::OnLayerAlphaShapeChanged() {}
 
-void LayerDelegate::OnLayerFillsBoundsOpaquelyChanged() {}
+void LayerDelegate::OnLayerFillsBoundsOpaquelyChanged(
+    PropertyChangeReason reason) {}
 
 void LayerDelegate::UpdateVisualState() {}
 
diff --git a/ui/compositor/layer_delegate.h b/ui/compositor/layer_delegate.h
index d89255f..bcdbce7 100644
--- a/ui/compositor/layer_delegate.h
+++ b/ui/compositor/layer_delegate.h
@@ -44,7 +44,9 @@
   virtual void OnLayerAlphaShapeChanged();
 
   // Invoked when whether the layer fills its bounds opaquely or not changed.
-  virtual void OnLayerFillsBoundsOpaquelyChanged();
+  // |reason| indicates whether the property was changed directly or by an
+  // animation.
+  virtual void OnLayerFillsBoundsOpaquelyChanged(PropertyChangeReason reason);
 
   // Called when it is a good opportunity for the delegate to update any visual
   // state or schedule any additional regions to be painted. Soon after this is
diff --git a/ui/display/manager/display_change_observer.cc b/ui/display/manager/display_change_observer.cc
index 1f26f77..28dbd10e 100644
--- a/ui/display/manager/display_change_observer.cc
+++ b/ui/display/manager/display_change_observer.cc
@@ -230,7 +230,8 @@
                             [](const DisplaySnapshot* display_state) {
                               return display_state->display_id();
                             });
-  return display_manager_->ShouldSetMirrorModeOn(list)
+  return display_manager_->ShouldSetMirrorModeOn(
+             list, /*should_check_hardware_mirrorring=*/true)
              ? MULTIPLE_DISPLAY_STATE_MULTI_MIRROR
              : MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
 }
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index d66c341e..9d4a6bf 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -842,18 +842,20 @@
 #if defined(OS_CHROMEOS)
   if (!configure_displays_ && new_display_info_list.size() > 1 &&
       hardware_mirroring_display_id_list.empty()) {
-    // Mirror mode is set by DisplayConfigurator on the device. Emulate it when
-    // running on linux desktop. Do not emulate it when hardware mirroring is
-    // on (This only happens in test).
     DisplayIdList list = GenerateDisplayIdList(
         new_display_info_list.begin(), new_display_info_list.end(),
         [](const ManagedDisplayInfo& display_info) {
           return display_info.id();
         });
+    // Mirror mode is set by DisplayConfigurator on the device. Emulate it when
+    // running on linux desktop.  Carry over HW mirroring state only in unified
+    // desktop so that it can switch to software mirroring to avoid exiting
+    // unified desktop.
+    // Note that this is only for testing.
     bool should_enable_software_mirroring =
         base::CommandLine::ForCurrentProcess()->HasSwitch(
             ::switches::kEnableSoftwareMirroring) ||
-        ShouldSetMirrorModeOn(list);
+        ShouldSetMirrorModeOn(list, unified_desktop_enabled_);
     SetSoftwareMirroring(should_enable_software_mirroring);
   }
 #endif
@@ -1299,7 +1301,9 @@
   return base::StringPrintf("Display %d", static_cast<int>(id));
 }
 
-bool DisplayManager::ShouldSetMirrorModeOn(const DisplayIdList& new_id_list) {
+bool DisplayManager::ShouldSetMirrorModeOn(
+    const DisplayIdList& new_id_list,
+    bool should_check_hardware_mirroring) {
   DCHECK(new_id_list.size() > 1);
   if (layout_store_->forced_mirror_mode_for_tablet())
     return true;
@@ -1329,7 +1333,8 @@
   }
   // Mirror mode should remain unchanged as long as there are more than one
   // connected displays.
-  return IsInMirrorMode();
+  return IsInSoftwareMirrorMode() ||
+         (should_check_hardware_mirroring && IsInHardwareMirrorMode());
 }
 
 void DisplayManager::SetMirrorMode(
diff --git a/ui/display/manager/display_manager.h b/ui/display/manager/display_manager.h
index 1c3a6a9..883da14 100644
--- a/ui/display/manager/display_manager.h
+++ b/ui/display/manager/display_manager.h
@@ -412,7 +412,10 @@
   std::string GetDisplayNameForId(int64_t id) const;
 
   // Returns true if mirror mode should be set on for the specified displays.
-  bool ShouldSetMirrorModeOn(const DisplayIdList& id_list);
+  // If |should_check_hardware_mirroring| is true, the state of
+  // IsInHardwareMirroringMode() will also be taken into account.
+  bool ShouldSetMirrorModeOn(const DisplayIdList& id_list,
+                             bool should_check_hardware_mirroring);
 
   // Change the mirror mode. |mixed_params| will be ignored if mirror mode is
   // off or normal. When mirror mode is off, display mode will be set to default
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.css b/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
index c3ead72..a2a1ea32 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.css
@@ -185,6 +185,12 @@
   width: 36px;
 }
 
+/* TODO(files-ng): files-ng rule that gets applied to non-files-ng */
+:host-context(html.pointer-active) cr-button:not(:active):hover {
+  --hover-bg-color: none;
+  cursor: unset;
+}
+
 :host([files-ng]) cr-button::after {
   content: '';
   height: 48px;
@@ -262,7 +268,8 @@
   font-weight: bold;
 }
 
-:host([files-ng]) cr-button:focus:not(:active) {
+/* TODO(files-ng): files-ng rule that gets applied to non-files-ng */
+:host-context(html.focus-outline-visible) cr-button:not(:active):focus {
   border: 2px solid var(--google-blue-300);
 }
 
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
index 799b8a0f..2c60d50b 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
@@ -34,7 +34,7 @@
          </cr-button>
            <files-icon-button toggles id="metadata-button" on-tap="onMetadataButtonTap_" active="{{metadataBoxActive}}" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" tabindex="0" has-tooltip>
            </files-icon-button>
-           <cr-button id="info-button" on-click="onMetadataButtonTap_" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" has-tooltip>
+           <cr-button id="info-button" on-click="onMetadataButtonTap_" aria-label="$i18n{QUICK_VIEW_TOGGLE_METADATA_BOX_BUTTON_LABEL}" has-tooltip toogle>
             <span class="icon"></span>
           </cr-button>
         </div>
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
index a3e58db..edc2561 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
@@ -182,6 +182,9 @@
   },
 
   /**
+   * TODO(992824): investigate the this.$.innerContentPanel.focus() since the
+   * has no tabindex it seems, and so the focus() call is ignored.
+   *
    * @param {!Event} event tap event.
    *
    * @private
@@ -193,11 +196,7 @@
 
     if (this.hasAttribute('files-ng')) {
       this.metadataBoxActive = !this.metadataBoxActive;
-      if (this.metadataBoxActive) {
-        event.target.setAttribute('toogle', '');
-      } else {
-        event.target.removeAttribute('toogle');
-      }
+      event.target.toggleAttribute('toogle', this.metadataBoxActive);
     }
   },
 
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index 2db83ae..6e9e693 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -764,8 +764,10 @@
 
     metrics.startInterval('Load.InitUI');
     if (util.isFilesNg()) {
+      this.document_.documentElement.classList.add('files-ng');
       this.dialogDom_.classList.add('files-ng');
     } else {
+      this.document_.documentElement.classList.remove('files-ng');
       this.dialogDom_.classList.remove('files-ng');
     }
     this.initEssentialUI_();
diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc
index f82e7db..a541814 100644
--- a/ui/message_center/views/message_view.cc
+++ b/ui/message_center/views/message_view.cc
@@ -460,7 +460,8 @@
 
 void MessageView::SetNestedBorderIfNecessary() {
   if (is_nested_) {
-    SkColor border_color = SkColorSetARGB(0x1F, 0x0, 0x0, 0x0);
+    SkColor border_color = GetNativeTheme()->GetSystemColor(
+        ui::NativeTheme::kColorId_UnfocusedBorderColor);
     SetBorder(views::CreateRoundedRectBorder(
         kNotificationBorderThickness, kNotificationCornerRadius, border_color));
   }
diff --git a/ui/snapshot/snapshot_aura_unittest.cc b/ui/snapshot/snapshot_aura_unittest.cc
index 0604185..df2b4371 100644
--- a/ui/snapshot/snapshot_aura_unittest.cc
+++ b/ui/snapshot/snapshot_aura_unittest.cc
@@ -34,7 +34,6 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/transform.h"
 #include "ui/gl/gl_implementation.h"
-#include "ui/wm/core/default_activation_client.h"
 
 namespace ui {
 namespace {
@@ -111,7 +110,6 @@
 
     helper_ = std::make_unique<aura::test::AuraTestHelper>();
     helper_->SetUp(context_factories_->GetContextFactory());
-    new ::wm::DefaultActivationClient(root_window());
   }
 
   void TearDown() override {
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 9ddb71cd..30dbf4a 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -45,15 +45,28 @@
   BubbleWidget() = default;
 
   // Widget:
+  // TODO(tluk) Fix child windows so that they inherit the theme properties of
+  // their parent. Currently this only fixes the problem for bubble dialogs
+  // anchored to another window. (https://crbug.com/948859)
   const ui::ThemeProvider* GetThemeProvider() const override {
-    BubbleDialogDelegateView* const bubble_delegate =
-        static_cast<BubbleDialogDelegateView*>(widget_delegate());
-    if (!bubble_delegate || !bubble_delegate->anchor_widget())
-      return Widget::GetThemeProvider();
-    return bubble_delegate->anchor_widget()->GetThemeProvider();
+    const Widget* anchor_widget = GetAnchorWidget();
+    return anchor_widget ? anchor_widget->GetThemeProvider()
+                         : Widget::GetThemeProvider();
+  }
+
+  const ui::NativeTheme* GetNativeTheme() const override {
+    const Widget* anchor_widget = GetAnchorWidget();
+    return anchor_widget ? anchor_widget->GetNativeTheme()
+                         : Widget::GetNativeTheme();
   }
 
  private:
+  const Widget* GetAnchorWidget() const {
+    BubbleDialogDelegateView* const bubble_delegate =
+        static_cast<BubbleDialogDelegateView*>(widget_delegate());
+    return bubble_delegate ? bubble_delegate->anchor_widget() : nullptr;
+  }
+
   DISALLOW_COPY_AND_ASSIGN(BubbleWidget);
 };
 
diff --git a/ui/views/corewm/tooltip_controller_unittest.cc b/ui/views/corewm/tooltip_controller_unittest.cc
index 985d6da..712e3fc 100644
--- a/ui/views/corewm/tooltip_controller_unittest.cc
+++ b/ui/views/corewm/tooltip_controller_unittest.cc
@@ -36,7 +36,6 @@
 #include "ui/views/view.h"
 #include "ui/views/widget/tooltip_manager.h"
 #include "ui/views/widget/widget.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/default_screen_position_client.h"
 #include "ui/wm/public/tooltip_client.h"
 
@@ -91,9 +90,6 @@
     ViewsTestBase::SetUp();
 
     aura::Window* root_window = GetContext();
-
-    if (root_window)
-      new wm::DefaultActivationClient(root_window);
 #if !BUILDFLAG(ENABLE_DESKTOP_AURA) || defined(OS_WIN)
     if (root_window) {
       tooltip_aura_ = new views::corewm::TooltipAura();
@@ -616,7 +612,6 @@
   void SetUp() override {
     at_exit_manager_ = std::make_unique<base::ShadowingAtExitManager>();
     aura::test::AuraTestBase::SetUp();
-    new wm::DefaultActivationClient(root_window());
     controller_ = std::make_unique<TooltipController>(
         std::unique_ptr<corewm::Tooltip>(test_tooltip_));
     root_window()->AddPreTargetHandler(controller_.get());
@@ -696,7 +691,6 @@
     ViewsTestBase::SetUp();
 
     aura::Window* root_window = GetContext();
-    new wm::DefaultActivationClient(root_window);
 
     widget_.reset(CreateWidget(root_window));
     widget_->SetContentsView(new View);
diff --git a/ui/views/test/views_test_helper_aura.cc b/ui/views/test/views_test_helper_aura.cc
index 2ab1aa0..8621b3e1 100644
--- a/ui/views/test/views_test_helper_aura.cc
+++ b/ui/views/test/views_test_helper_aura.cc
@@ -6,7 +6,6 @@
 
 #include "ui/aura/client/screen_position_client.h"
 #include "ui/wm/core/capture_controller.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/default_screen_position_client.h"
 
 namespace views {
@@ -20,15 +19,11 @@
   aura_test_helper_.SetUp();
 
   gfx::NativeWindow root_window = GetContext();
-  if (root_window) {
-    new wm::DefaultActivationClient(root_window);
-
-    if (!aura::client::GetScreenPositionClient(root_window)) {
-      screen_position_client_ =
-          std::make_unique<wm::DefaultScreenPositionClient>();
-      aura::client::SetScreenPositionClient(root_window,
-                                            screen_position_client_.get());
-    }
+  if (root_window && !aura::client::GetScreenPositionClient(root_window)) {
+    screen_position_client_ =
+        std::make_unique<wm::DefaultScreenPositionClient>();
+    aura::client::SetScreenPositionClient(root_window,
+                                          screen_position_client_.get());
   }
 }
 
diff --git a/ui/wm/core/compound_event_filter_unittest.cc b/ui/wm/core/compound_event_filter_unittest.cc
index fe5f80dc..8bf30703 100644
--- a/ui/wm/core/compound_event_filter_unittest.cc
+++ b/ui/wm/core/compound_event_filter_unittest.cc
@@ -17,7 +17,6 @@
 #include "ui/events/event_utils.h"
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/test/event_generator.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/public/activation_client.h"
 
 namespace {
@@ -114,7 +113,6 @@
 #if defined(OS_CHROMEOS) || defined(OS_WIN)
 // Touch visually hides the cursor on ChromeOS and Windows.
 TEST_F(CompoundEventFilterTest, TouchHidesCursor) {
-  new wm::DefaultActivationClient(root_window());
   std::unique_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter);
   aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get());
   aura::test::TestWindowDelegate delegate;
diff --git a/ui/wm/core/shadow_controller_unittest.cc b/ui/wm/core/shadow_controller_unittest.cc
index 8b61b5a..4ca5ca6e 100644
--- a/ui/wm/core/shadow_controller_unittest.cc
+++ b/ui/wm/core/shadow_controller_unittest.cc
@@ -16,7 +16,6 @@
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor_extra/shadow.h"
-#include "ui/wm/core/default_activation_client.h"
 #include "ui/wm/core/shadow_controller_delegate.h"
 #include "ui/wm/core/shadow_types.h"
 #include "ui/wm/core/window_util.h"
@@ -31,7 +30,6 @@
 
   void SetUp() override {
     AuraTestBase::SetUp();
-    new wm::DefaultActivationClient(root_window());
     InstallShadowController(nullptr);
   }
   void TearDown() override {
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
index 4ff35aa7..78e5971 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
@@ -293,4 +293,36 @@
         callbackHelper.waitForCallback(callCount);
         Assert.assertEquals(false, isTabModalShowingResult[0]);
     }
+
+    @Test
+    @SmallTest
+    public void testOnTitleUpdated() throws TimeoutException {
+        String startupUrl = "about:blank";
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(startupUrl);
+
+        String titles[] = new String[1];
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            Tab tab = activity.getTab();
+            TabCallback callback = new TabCallback() {
+                @Override
+                public void onTitleUpdated(String title) {
+                    titles[0] = title;
+                }
+            };
+            tab.registerTabCallback(callback);
+        });
+
+        String url = mActivityTestRule.getTestDataURL("simple_page.html");
+        mActivityTestRule.navigateAndWait(url);
+        // Use polling because title is allowed to go through multiple transitions.
+        CriteriaHelper.pollUiThread(() -> { return "OK".equals(titles[0]); });
+
+        url = mActivityTestRule.getTestDataURL("shakespeare.html");
+        mActivityTestRule.navigateAndWait(url);
+        CriteriaHelper.pollUiThread(() -> { return titles[0].endsWith("shakespeare.html"); });
+
+        mActivityTestRule.executeScriptSync("document.title = \"foobar\";", false);
+        Assert.assertEquals("foobar", titles[0]);
+        CriteriaHelper.pollUiThread(() -> { return "foobar".equals(titles[0]); });
+    }
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java b/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java
index 4e63f53..fad37a2 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java
@@ -10,6 +10,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.weblayer_private.interfaces.ITabClient;
+import org.chromium.weblayer_private.interfaces.ObjectWrapper;
 
 /**
  * Owns the C++ TabCallbackProxy class, which is responsible for forwarding all
@@ -41,6 +42,12 @@
         mClient.onRenderProcessGone();
     }
 
+    @CalledByNative
+    private void onTitleUpdated(String title) throws RemoteException {
+        if (WebLayerFactoryImpl.getClientMajorVersion() < 83) return;
+        mClient.onTitleUpdated(ObjectWrapper.wrap(title));
+    }
+
     @NativeMethods
     interface Natives {
         long createTabCallbackProxy(TabCallbackProxy proxy, long tab);
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
index 6c8a482..28a048de 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl
@@ -26,4 +26,7 @@
 
   // Added in M82.
   void onTabModalStateChanged(in boolean isTabModalShowing) = 5;
+
+  // Added in M83.
+  void onTitleUpdated(in IObjectWrapper title) = 6;
 }
diff --git a/weblayer/browser/tab_callback_proxy.cc b/weblayer/browser/tab_callback_proxy.cc
index 6c8adaf..5b17bab 100644
--- a/weblayer/browser/tab_callback_proxy.cc
+++ b/weblayer/browser/tab_callback_proxy.cc
@@ -39,6 +39,12 @@
                                             java_observer_);
 }
 
+void TabCallbackProxy::OnTitleUpdated(const base::string16& title) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_TabCallbackProxy_onTitleUpdated(
+      env, java_observer_, base::android::ConvertUTF16ToJavaString(env, title));
+}
+
 static jlong JNI_TabCallbackProxy_CreateTabCallbackProxy(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& proxy,
diff --git a/weblayer/browser/tab_callback_proxy.h b/weblayer/browser/tab_callback_proxy.h
index d71287af..8adfd2d 100644
--- a/weblayer/browser/tab_callback_proxy.h
+++ b/weblayer/browser/tab_callback_proxy.h
@@ -25,6 +25,7 @@
   // TabObserver:
   void DisplayedUrlChanged(const GURL& url) override;
   void OnRenderProcessGone() override;
+  void OnTitleUpdated(const base::string16& title) override;
 
  private:
   Tab* tab_;
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 61fd39d..5e0b944 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -231,6 +231,7 @@
   // partially destructed. DidFinishNavigation can be called while destroying
   // WebContents, so stop observing first.
   Observe(nullptr);
+  web_contents_->SetDelegate(nullptr);
   web_contents_.reset();
 }
 
@@ -450,11 +451,26 @@
 
 void TabImpl::NavigationStateChanged(content::WebContents* source,
                                      content::InvalidateTypes changed_flags) {
+  DCHECK_EQ(web_contents_.get(), source);
   if (changed_flags & content::INVALIDATE_TYPE_URL) {
     for (auto& observer : observers_)
       observer.DisplayedUrlChanged(source->GetVisibleURL());
     UpdateBrowserVisibleSecurityStateIfNecessary();
   }
+
+  // TODO(crbug.com/1064582): INVALIDATE_TYPE_TITLE is called only when a title
+  // is set on the active navigation entry, but not when the active entry
+  // changes, so check INVALIDATE_TYPE_LOAD here as well. However this should
+  // be fixed and INVALIDATE_TYPE_LOAD should be removed.
+  if (changed_flags &
+      (content::INVALIDATE_TYPE_TITLE | content::INVALIDATE_TYPE_LOAD)) {
+    base::string16 title = web_contents_->GetTitle();
+    if (title_ != title) {
+      title_ = title;
+      for (auto& observer : observers_)
+        observer.OnTitleUpdated(title);
+    }
+  }
 }
 
 content::JavaScriptDialogManager* TabImpl::GetJavaScriptDialogManager(
diff --git a/weblayer/browser/tab_impl.h b/weblayer/browser/tab_impl.h
index 5ab828e..f83e3fb3 100644
--- a/weblayer/browser/tab_impl.h
+++ b/weblayer/browser/tab_impl.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/strings/string16.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "components/find_in_page/find_result_observer.h"
@@ -251,6 +252,8 @@
 
   const std::string guid_;
 
+  base::string16 title_;
+
   base::WeakPtrFactory<TabImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabImpl);
diff --git a/weblayer/public/java/org/chromium/weblayer/Tab.java b/weblayer/public/java/org/chromium/weblayer/Tab.java
index 8bd304b2..d55c900f 100644
--- a/weblayer/public/java/org/chromium/weblayer/Tab.java
+++ b/weblayer/public/java/org/chromium/weblayer/Tab.java
@@ -368,6 +368,15 @@
                 callback.onTabModalStateChanged(isTabModalShowing);
             }
         }
+
+        @Override
+        public void onTitleUpdated(IObjectWrapper title) {
+            StrictModeWorkaround.apply();
+            String titleString = ObjectWrapper.unwrap(title, String.class);
+            for (TabCallback callback : mCallbacks) {
+                callback.onTitleUpdated(titleString);
+            }
+        }
     }
 
     private static final class DownloadCallbackClientImpl extends IDownloadCallbackClient.Stub {
diff --git a/weblayer/public/java/org/chromium/weblayer/TabCallback.java b/weblayer/public/java/org/chromium/weblayer/TabCallback.java
index 4a44a794..5f22835 100644
--- a/weblayer/public/java/org/chromium/weblayer/TabCallback.java
+++ b/weblayer/public/java/org/chromium/weblayer/TabCallback.java
@@ -40,4 +40,12 @@
      * @since 82
      */
     public void onTabModalStateChanged(boolean isTabModalShowing) {}
+
+    /**
+     * Called when the title of this tab changes. Note before the page sets a title, the title may
+     * be a portion of the Uri.
+     * @param title New title of this tab.
+     * @since 83
+     */
+    public void onTitleUpdated(@NonNull String title) {}
 }
diff --git a/weblayer/public/tab_observer.h b/weblayer/public/tab_observer.h
index ca3a9e1..b853a3ec 100644
--- a/weblayer/public/tab_observer.h
+++ b/weblayer/public/tab_observer.h
@@ -5,6 +5,8 @@
 #ifndef WEBLAYER_PUBLIC_TAB_OBSERVER_H_
 #define WEBLAYER_PUBLIC_TAB_OBSERVER_H_
 
+#include "base/strings/string16.h"
+
 class GURL;
 
 namespace weblayer {
@@ -18,6 +20,10 @@
   // the system to reclaim memory.
   virtual void OnRenderProcessGone() {}
 
+  // Called when the title of this tab changes. Note before the page sets a
+  // title, the title may be a portion of the Uri.
+  virtual void OnTitleUpdated(const base::string16& title) {}
+
  protected:
   virtual ~TabObserver() {}
 };