diff --git a/BUILD.gn b/BUILD.gn
index f69c7f62..48eab0e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1291,12 +1291,6 @@
   }
 }
 
-# TODO(cassew): Add more OS's that don't support x86
-assert(
-    !(target_cpu == "x86" &&
-          (target_os == "ios" || target_os == "mac" || target_os == "linux")),
-    "'target_cpu=x86' is not supported for 'target_os=$target_os'. Consider omitting 'target_cpu' (default) or using 'target_cpu=x64' instead.")
-
 group("chromium_builder_perf") {
   testonly = true
 
diff --git a/DEPS b/DEPS
index bfee5e2..3265d6c 100644
--- a/DEPS
+++ b/DEPS
@@ -204,11 +204,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': '92748af1a5051e9fe329c8200f0fa3b47aadbdd7',
+  'skia_revision': 'ead52dc068d55efdebda41d2e8eef13a628f9f2f',
   # 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': '6b214099c60660a75b414785481afafc74a50628',
+  'v8_revision': '4e2fbf2b6d4fc2e9bbf239e146973a15bb3a0d40',
   # 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.
@@ -216,7 +216,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': '06d194e2ae7b1d7e0eda0c0c911eff92dec7d3d1',
+  'angle_revision': '687d3153197e5ef162e7bc75e931a09208ba3fe6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -275,7 +275,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': '6e87bddf1b604c66707e6bd7590f905a8ff95e92',
+  'catapult_revision': '8efb1d91dddd775f95684924426e6f59f0c8f07b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -697,7 +697,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'B7qM_AW6UDDOFU7yJCMYbX-Vd-nE-o2Flr7rs4bAvMYC',
+          'version': 'gRuwnwZrRAywjOPntIYH8-K7mi8twfkj8yOFVr08O2UC',
       },
     ],
     'condition': 'checkout_android',
@@ -1302,7 +1302,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4973272defb4081572496c831ca3e55c6d12c99a',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0f1ec9f510ae0cc8f800d08e8205017b6a8b8f3b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1511,7 +1511,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '991335be3de503ef02cd9f8415e4242ad3f107f9',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@2be540b3efbccbb11308b45ebbce9c01041b8684',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@105af117f0532953577198cc9bd8ee6f76c29009',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1586,7 +1586,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'j4rOmbVNLoVaUWWPvzH9y8NIrbGae5OxIInpCD89q0QC',
+          'version': '5QaZrGtCcd9HFM-qeg4vP-CrEpvhqlZtkfwAtVBee2oC',
         },
       ],
       'dep_type': 'cipd',
@@ -1596,7 +1596,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'VUCuq1sveo1hxWfABmHwe-nXmh0OuWy4y9PhS3ykvykC',
+          'version': 'F9PFp7kNc3dn_X7b1PYmPsEb3VMHDY-8pXiZ7WNi09MC',
         },
       ],
       'dep_type': 'cipd',
@@ -1610,7 +1610,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8a049f5cad963801d946d9d0dc078c514fe981e9',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@551a97a6caf8e41bc659ed28a7530950fd1a78a1',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 28c8cd6f..8300a4e 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -364,7 +364,7 @@
     AwBrowserContext getBrowserContextOnUiThread() {
         assert mInitState == INIT_FINISHED;
 
-        if (BuildConfig.DCHECK_IS_ON && !ThreadUtils.runningOnUiThread()) {
+        if (BuildConfig.ENABLE_ASSERTS && !ThreadUtils.runningOnUiThread()) {
             throw new RuntimeException(
                     "getBrowserContextOnUiThread called on " + Thread.currentThread());
         }
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index e9d1dd4..7d6f191 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -501,10 +501,6 @@
 const base::Feature kOsSettingsDeepLinking{"OsSettingsDeepLinking",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Flips chrome://os-settings to show Polymer 3 version
-const base::Feature kOsSettingsPolymer3{"OsSettingsPolymer3",
-                                        base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Provides a UI for users to view information about their Android phone
 // and perform phone-side actions within Chrome OS.
 const base::Feature kPhoneHub{"PhoneHub", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index fa85e908..b468ff4 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -226,8 +226,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kOsSettingsDeepLinking;
 COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const base::Feature kOsSettingsPolymer3;
-COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kPhoneHub;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kPinSetupForFamilyLink;
diff --git a/ash/metrics/login_unlock_throughput_recorder.cc b/ash/metrics/login_unlock_throughput_recorder.cc
index 6778bfe..b12bd71 100644
--- a/ash/metrics/login_unlock_throughput_recorder.cc
+++ b/ash/metrics/login_unlock_throughput_recorder.cc
@@ -24,41 +24,48 @@
              : "ClamshellMode";
 }
 
-void ReportLogin(const cc::FrameSequenceMetrics::CustomReportData& data) {
-  if (data.frames_expected) {
-    int smoothness = metrics_util::CalculateSmoothness(data);
-    int jank = metrics_util::CalculateJank(data);
+void RecordMetrics(const base::TimeTicks& start,
+                   const cc::FrameSequenceMetrics::CustomReportData& data,
+                   const char* smoothness_name,
+                   const char* jank_name,
+                   const char* duration_name) {
+  DCHECK(data.frames_expected);
+  int duration_ms = (base::TimeTicks::Now() - start).InMilliseconds();
+  int smoothness, jank;
+  smoothness = metrics_util::CalculateSmoothness(data);
+  jank = metrics_util::CalculateJank(data);
 
-    float refresh_rate =
-        Shell::GetPrimaryRootWindow()->GetHost()->compositor()->refresh_rate();
-    int duration_ms = (1000.f / refresh_rate) * data.frames_expected;
-    std::string suffix = GetDeviceModeSuffix();
-    base::UmaHistogramPercentage("Ash.LoginAnimation.Smoothness." + suffix,
-                                 smoothness);
-    base::UmaHistogramPercentage("Ash.LoginAnimation.Jank." + suffix, jank);
-    // TODO(crbug.com/1143898): Deprecate this metrics once the login
-    // performance issue is resolved.
+  std::string suffix = GetDeviceModeSuffix();
+  base::UmaHistogramPercentage(smoothness_name + suffix, smoothness);
+  base::UmaHistogramPercentage(jank_name + suffix, jank);
+  // TODO(crbug.com/1143898): Deprecate this metrics once the login/unlock
+  // performance issue is resolved.
+  if (duration_name) {
     base::UmaHistogramCustomTimes(
-        "Ash.LoginAnimation.Duration." + suffix,
-        base::TimeDelta::FromMilliseconds(duration_ms),
+        duration_name + suffix, base::TimeDelta::FromMilliseconds(duration_ms),
         base::TimeDelta::FromMilliseconds(100), base::TimeDelta::FromSeconds(5),
         50);
-  } else {
-    LOG(WARNING) << "Zero frames expected in login animation throughput data";
   }
 }
 
-void ReportUnlock(const cc::FrameSequenceMetrics::CustomReportData& data) {
-  if (data.frames_expected) {
-    int smoothness = metrics_util::CalculateSmoothness(data);
-    int jank = metrics_util::CalculateJank(data);
-    std::string suffix = GetDeviceModeSuffix();
-    base::UmaHistogramPercentage("Ash.UnlockAnimation.Smoothness." + suffix,
-                                 smoothness);
-    base::UmaHistogramPercentage("Ash.UnlockAnimation.Jank." + suffix, jank);
-  } else {
-    LOG(WARNING) << "Zero frames expected in Unlock animation throughput data";
+void ReportLogin(base::TimeTicks start,
+                 const cc::FrameSequenceMetrics::CustomReportData& data) {
+  if (!data.frames_expected) {
+    LOG(WARNING) << "Zero frames expected in login animation throughput data";
+    return;
   }
+  RecordMetrics(start, data, "Ash.LoginAnimation.Smoothness.",
+                "Ash.LoginAnimation.Jank.", "Ash.LoginAnimation.Duration.");
+}
+
+void ReportUnlock(base::TimeTicks start,
+                  const cc::FrameSequenceMetrics::CustomReportData& data) {
+  if (!data.frames_expected) {
+    LOG(WARNING) << "Zero frames expected in unlock animation throughput data";
+    return;
+  }
+  RecordMetrics(start, data, "Ash.UnlockAnimation.Smoothness.",
+                "Ash.UnlockAnimation.Jank.", nullptr);
 }
 
 }  // namespace
@@ -81,7 +88,8 @@
        logged_in_user == chromeos::LoginState::LOGGED_IN_USER_REGULAR)) {
     auto* primary_root = Shell::GetPrimaryRootWindow();
     new ui::TotalAnimationThroughputReporter(
-        primary_root->GetHost()->compositor(), base::BindOnce(&ReportUnlock),
+        primary_root->GetHost()->compositor(),
+        base::BindOnce(&ReportUnlock, base::TimeTicks::Now()),
         /*self_destruct=*/true);
   }
 }
@@ -94,7 +102,8 @@
        logged_in_user == chromeos::LoginState::LOGGED_IN_USER_REGULAR)) {
     auto* primary_root = Shell::GetPrimaryRootWindow();
     new ui::TotalAnimationThroughputReporter(
-        primary_root->GetHost()->compositor(), base::BindOnce(&ReportLogin),
+        primary_root->GetHost()->compositor(),
+        base::BindOnce(&ReportLogin, base::TimeTicks::Now()),
         /*self_destruct=*/true);
   }
 }
diff --git a/ash/public/cpp/app_list/app_list_metrics.h b/ash/public/cpp/app_list/app_list_metrics.h
index 9e57f8e..ed96218 100644
--- a/ash/public/cpp/app_list/app_list_metrics.h
+++ b/ash/public/cpp/app_list/app_list_metrics.h
@@ -102,6 +102,10 @@
   OMNIBOX_RICH_ENTITY_ANSWER,
   // A rich entity result from omnibox with image icon.
   OMNIBOX_RICH_ENTITY_IMAGE_ENTITY,
+  // A local file search result.
+  LOCAL_FILE_SEARCH,
+  // A Drive file search result.
+  DRIVE_FILE_SEARCH,
   // Boundary is always last.
   SEARCH_RESULT_TYPE_BOUNDARY
 };
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 46d8b8f9..ff25296 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -279,6 +279,10 @@
     is_auto_hide_state_locked_ = lock_auto_hide_state;
   }
 
+  ShelfAutoHideBehavior auto_hide_behavior() const {
+    return shelf_->auto_hide_behavior();
+  }
+
   // ShelfConfig::Observer:
   void OnShelfConfigUpdated() override;
 
diff --git a/ash/wm/overview/overview_window_drag_controller_unittest.cc b/ash/wm/overview/overview_window_drag_controller_unittest.cc
index 6c0bd360..1636ccf 100644
--- a/ash/wm/overview/overview_window_drag_controller_unittest.cc
+++ b/ash/wm/overview/overview_window_drag_controller_unittest.cc
@@ -191,32 +191,6 @@
   EXPECT_TRUE(overview_session->no_windows_widget_for_testing());
 }
 
-// Test that if window is destroyed during dragging, no crash should happen and
-// drag should be reset.
-TEST_F(OverviewWindowDragControllerTest, WindowDestroyedDuringDragging) {
-  std::unique_ptr<aura::Window> window =
-      CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  auto* overview_controller = Shell::Get()->overview_controller();
-  overview_controller->StartOverview();
-  EXPECT_TRUE(overview_controller->InOverviewSession());
-  auto* overview_session = overview_controller->overview_session();
-  auto* overview_item =
-      overview_session->GetOverviewItemForWindow(window.get());
-  ASSERT_TRUE(overview_item);
-
-  auto* event_generator = GetEventGenerator();
-  StartDraggingItemBy(overview_item, 30, 200, /*by_touch_gestures=*/false,
-                      event_generator);
-  OverviewWindowDragController* drag_controller =
-      overview_session->window_drag_controller();
-  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
-            drag_controller->current_drag_behavior());
-
-  window.reset();
-  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNoDrag,
-            drag_controller->current_drag_behavior());
-}
-
 // Tests the behavior of dragging a window in portrait tablet mode with virtual
 // desks enabled.
 class OverviewWindowDragControllerDesksPortraitTabletTest : public AshTestBase {
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 2ea9f93..c4f5d0f 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -4844,8 +4844,6 @@
         ->current_window_dragging_state();
   }
 
-  void DestroyWindow() { window_.reset(); }
-
   aura::Window* window() { return window_.get(); }
 
   std::unique_ptr<TabletModeWindowResizer> controller_;
@@ -5240,28 +5238,4 @@
   EXPECT_EQ(backdrop_window->bounds(), active_desk_container->bounds());
 }
 
-TEST_F(SplitViewAppDraggingTest, WindowDestroyedDuringDragging) {
-  UpdateDisplay("800x600");
-  InitializeWindow(/*can_resize=*/true);
-  EXPECT_TRUE(WindowState::Get(window())->IsMaximized());
-  gfx::Rect display_bounds =
-      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
-          window());
-
-  // Start app dragging.
-  gfx::PointF location;
-  const float long_scroll_delta = display_bounds.height() / 4 + 5;
-  location.set_y(long_scroll_delta);
-  SendScrollStartAndUpdate(location);
-  OverviewController* overview_controller = Shell::Get()->overview_controller();
-  EXPECT_TRUE(overview_controller->InOverviewSession());
-  EXPECT_FALSE(
-      overview_controller->overview_session()->IsWindowInOverview(window()));
-
-  // Destroy the window.
-  DestroyWindow();
-  // Overview should still be active.
-  EXPECT_TRUE(overview_controller->InOverviewSession());
-}
-
 }  // namespace ash
diff --git a/ash/wm/toplevel_window_event_handler.cc b/ash/wm/toplevel_window_event_handler.cc
index 426963e8..0ff0659 100644
--- a/ash/wm/toplevel_window_event_handler.cc
+++ b/ash/wm/toplevel_window_event_handler.cc
@@ -456,74 +456,6 @@
   }
 }
 
-wm::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
-    aura::Window* source,
-    const gfx::Vector2d& drag_offset,
-    ::wm::WindowMoveSource move_source) {
-  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
-  aura::Window* root_window = source->GetRootWindow();
-  DCHECK(root_window);
-  gfx::PointF drag_location;
-  if (move_source == ::wm::WINDOW_MOVE_SOURCE_TOUCH &&
-      aura::Env::GetInstance()->is_touch_down()) {
-    gfx::PointF drag_location_f;
-    bool has_point = aura::Env::GetInstance()
-                         ->gesture_recognizer()
-                         ->GetLastTouchPointForTarget(source, &drag_location_f);
-    drag_location = drag_location_f;
-    DCHECK(has_point);
-  } else {
-    drag_location = gfx::PointF(
-        root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
-    aura::Window::ConvertPointToTarget(root_window, source->parent(),
-                                       &drag_location);
-  }
-  // Set the cursor before calling AttemptToStartDrag(), as that will
-  // eventually call LockCursor() and prevent the cursor from changing.
-  aura::client::CursorClient* cursor_client =
-      aura::client::GetCursorClient(root_window);
-  if (cursor_client)
-    cursor_client->SetCursor(ui::mojom::CursorType::kPointer);
-
-  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
-
-  DragResult result = DragResult::SUCCESS;
-  if (!AttemptToStartDrag(source, drag_location, HTCAPTION, move_source,
-                          base::BindOnce(OnDragCompleted, &result, &run_loop),
-                          /*update_gesture_target=*/false)) {
-    return ::wm::MOVE_CANCELED;
-  }
-
-  in_move_loop_ = true;
-  base::WeakPtr<ToplevelWindowEventHandler> weak_ptr(
-      weak_factory_.GetWeakPtr());
-
-  // Disable window position auto management while dragging and restore it
-  // aftrewards.
-  WindowState* window_state = WindowState::Get(source);
-  const bool window_position_managed = window_state->GetWindowPositionManaged();
-  window_state->SetWindowPositionManaged(false);
-  aura::WindowTracker tracker({source});
-
-  run_loop.Run();
-
-  if (!weak_ptr)
-    return ::wm::MOVE_CANCELED;
-
-  // Make sure the window hasn't been deleted.
-  if (tracker.Contains(source))
-    window_state->SetWindowPositionManaged(window_position_managed);
-
-  in_move_loop_ = false;
-  return result == DragResult::SUCCESS ? ::wm::MOVE_SUCCESSFUL
-                                       : ::wm::MOVE_CANCELED;
-}
-
-void ToplevelWindowEventHandler::EndMoveLoop() {
-  if (in_move_loop_)
-    RevertDrag();
-}
-
 bool ToplevelWindowEventHandler::AttemptToStartDrag(
     aura::Window* window,
     const gfx::PointF& point_in_parent,
@@ -628,6 +560,74 @@
   return nullptr;
 }
 
+::wm::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
+    aura::Window* source,
+    const gfx::Vector2d& drag_offset,
+    ::wm::WindowMoveSource move_source) {
+  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
+  aura::Window* root_window = source->GetRootWindow();
+  DCHECK(root_window);
+  gfx::PointF drag_location;
+  if (move_source == ::wm::WINDOW_MOVE_SOURCE_TOUCH &&
+      aura::Env::GetInstance()->is_touch_down()) {
+    gfx::PointF drag_location_f;
+    bool has_point = aura::Env::GetInstance()
+                         ->gesture_recognizer()
+                         ->GetLastTouchPointForTarget(source, &drag_location_f);
+    drag_location = drag_location_f;
+    DCHECK(has_point);
+  } else {
+    drag_location = gfx::PointF(
+        root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
+    aura::Window::ConvertPointToTarget(root_window, source->parent(),
+                                       &drag_location);
+  }
+  // Set the cursor before calling AttemptToStartDrag(), as that will
+  // eventually call LockCursor() and prevent the cursor from changing.
+  aura::client::CursorClient* cursor_client =
+      aura::client::GetCursorClient(root_window);
+  if (cursor_client)
+    cursor_client->SetCursor(ui::mojom::CursorType::kPointer);
+
+  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+
+  DragResult result = DragResult::SUCCESS;
+  if (!AttemptToStartDrag(source, drag_location, HTCAPTION, move_source,
+                          base::BindOnce(OnDragCompleted, &result, &run_loop),
+                          /*update_gesture_target=*/false)) {
+    return ::wm::MOVE_CANCELED;
+  }
+
+  in_move_loop_ = true;
+  base::WeakPtr<ToplevelWindowEventHandler> weak_ptr(
+      weak_factory_.GetWeakPtr());
+
+  // Disable window position auto management while dragging and restore it
+  // aftrewards.
+  WindowState* window_state = WindowState::Get(source);
+  const bool window_position_managed = window_state->GetWindowPositionManaged();
+  window_state->SetWindowPositionManaged(false);
+  aura::WindowTracker tracker({source});
+
+  run_loop.Run();
+
+  if (!weak_ptr)
+    return ::wm::MOVE_CANCELED;
+
+  // Make sure the window hasn't been deleted.
+  if (tracker.Contains(source))
+    window_state->SetWindowPositionManaged(window_position_managed);
+
+  in_move_loop_ = false;
+  return result == DragResult::SUCCESS ? ::wm::MOVE_SUCCESSFUL
+                                       : ::wm::MOVE_CANCELED;
+}
+
+void ToplevelWindowEventHandler::EndMoveLoop() {
+  if (in_move_loop_)
+    RevertDrag();
+}
+
 bool ToplevelWindowEventHandler::PrepareForDrag(
     aura::Window* window,
     const gfx::PointF& point_in_parent,
diff --git a/ash/wm/toplevel_window_event_handler.h b/ash/wm/toplevel_window_event_handler.h
index 8d9bd16..f8c26da 100644
--- a/ash/wm/toplevel_window_event_handler.h
+++ b/ash/wm/toplevel_window_event_handler.h
@@ -68,12 +68,6 @@
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
-  // wm::WindowMoveClient:
-  wm::WindowMoveResult RunMoveLoop(aura::Window* source,
-                                   const gfx::Vector2d& drag_offset,
-                                   ::wm::WindowMoveSource move_source) override;
-  void EndMoveLoop() override;
-
   // Attempts to start a drag if one is not already in progress. Returns true if
   // successful. |end_closure| is run when the drag completes, including if the
   // drag is not started. If |update_gesture_target| is true, the gesture
@@ -110,8 +104,12 @@
     return event_location_in_gesture_target_;
   }
 
-  // Returns true if there is a drag in progress.
-  bool is_drag_in_progress() const { return window_resizer_.get() != nullptr; }
+  // Overridden from wm::WindowMoveClient:
+  ::wm::WindowMoveResult RunMoveLoop(
+      aura::Window* source,
+      const gfx::Vector2d& drag_offset,
+      ::wm::WindowMoveSource move_source) override;
+  void EndMoveLoop() override;
 
  private:
   class ScopedWindowResizer;
diff --git a/ash/wm/window_cycle/window_cycle_controller.cc b/ash/wm/window_cycle/window_cycle_controller.cc
index e680efb..96faf822 100644
--- a/ash/wm/window_cycle/window_cycle_controller.cc
+++ b/ash/wm/window_cycle/window_cycle_controller.cc
@@ -29,7 +29,6 @@
 #include "ash/wm/window_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -432,8 +431,7 @@
       window_util::GetActiveWindow();
 
   // Remove our key event filter.
-  base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
-                                                  event_filter_.release());
+  event_filter_.reset();
 
   if (active_window_after_window_cycle != nullptr &&
       active_window_before_window_cycle_ != active_window_after_window_cycle) {
diff --git a/ash/wm/window_cycle/window_cycle_controller_unittest.cc b/ash/wm/window_cycle/window_cycle_controller_unittest.cc
index a522be8..48c9653 100644
--- a/ash/wm/window_cycle/window_cycle_controller_unittest.cc
+++ b/ash/wm/window_cycle/window_cycle_controller_unittest.cc
@@ -1426,6 +1426,49 @@
   EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
 }
 
+// Tests that pressing the enter key or space key really quickly doesn't crash.
+// See crbug.com/1187242.
+TEST_F(InteractiveWindowCycleControllerTest, RapidConfirmSelection) {
+  std::unique_ptr<Window> w0 = CreateTestWindow();
+  std::unique_ptr<Window> w1 = CreateTestWindow();
+  std::unique_ptr<Window> w2 = CreateTestWindow();
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
+
+  // Start cycling and press space twice. This should not crash.
+  controller->StartCycling();
+  controller->HandleCycleWindow(
+      WindowCycleController::WindowCyclingDirection::kForward);
+  generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
+  generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
+  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+  // Start cycling and press enter twice. This should not crash.
+  controller->StartCycling();
+  controller->HandleCycleWindow(
+      WindowCycleController::WindowCyclingDirection::kForward);
+  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
+  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
+  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+
+  // Press down alt and tab. Release alt key and press enter. This should not
+  // crash.
+  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
+  EXPECT_TRUE(controller->IsCycling());
+  generator->ReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
+  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
+  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+  // Start cycling and press enter once and then right key. This should not
+  // crash and the right key should not affect the selection.
+  controller->StartCycling();
+  controller->HandleCycleWindow(
+      WindowCycleController::WindowCyclingDirection::kForward);
+  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
+  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
+  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+}
+
 // Tests that mouse events are filtered until the mouse is actually used,
 // preventing the mouse from unexpectedly triggering events.
 // See crbug.com/1143275.
diff --git a/ash/wm/window_cycle/window_cycle_event_filter.cc b/ash/wm/window_cycle/window_cycle_event_filter.cc
index 8f18e151..579620a3 100644
--- a/ash/wm/window_cycle/window_cycle_event_filter.cc
+++ b/ash/wm/window_cycle/window_cycle_event_filter.cc
@@ -128,10 +128,7 @@
 
 void WindowCycleEventFilter::OnGestureEvent(ui::GestureEvent* event) {
   if (features::IsInteractiveWindowCycleListEnabled()) {
-    if (ProcessGestureEvent(event)) {
-      event->SetHandled();
-      event->StopPropagation();
-    }
+    ProcessGestureEvent(event);
   } else {
     // Prevent any form of tap from doing anything while the Alt+Tab UI is
     // active.
@@ -240,7 +237,8 @@
   }
 }
 
-bool WindowCycleEventFilter::ProcessGestureEvent(ui::GestureEvent* event) {
+void WindowCycleEventFilter::ProcessGestureEvent(ui::GestureEvent* event) {
+  bool should_complete_cycling = false;
   switch (event->type()) {
     case ui::ET_GESTURE_TAP:
     case ui::ET_GESTURE_TAP_DOWN:
@@ -252,57 +250,62 @@
       tapped_window_ =
           Shell::Get()->window_cycle_controller()->GetWindowAtPoint(
               event->AsLocatedEvent());
-      return true;
+      break;
     }
     case ui::ET_GESTURE_TAP_CANCEL:
       // Do nothing because the event after this one determines whether we
       // scrolled or tapped.
-      return true;
+      break;
     case ui::ET_GESTURE_SCROLL_BEGIN: {
       tapped_window_ = nullptr;
       if (!Shell::Get()->window_cycle_controller()->IsEventInCycleView(event))
-        return false;
+        return;
 
       touch_scrolling_ = true;
-      return true;
+      break;
     }
     case ui::ET_GESTURE_SCROLL_UPDATE: {
       if (!touch_scrolling_)
-        return false;
+        return;
 
       Shell::Get()->window_cycle_controller()->Drag(
           event->details().scroll_x());
-      return true;
+      break;
     }
     case ui::ET_SCROLL_FLING_START: {
       tapped_window_ = nullptr;
       auto* window_cycle_controller = Shell::Get()->window_cycle_controller();
       if (!window_cycle_controller->IsEventInCycleView(event))
-        return false;
+        return;
 
       window_cycle_controller->StartFling(event->details().velocity_x());
-      return true;
+      break;
     }
     case ui::ET_GESTURE_END: {
       if (tapped_window_) {
-        // |this| will be destroyed after this line.
-        Shell::Get()->window_cycle_controller()->CompleteCycling();
+        // Defer calling WindowCycleController::CompleteCycling() until we've
+        // set |event| to handled and stop its propagation.
+        should_complete_cycling = true;
       } else {
         tapped_window_ = nullptr;
         touch_scrolling_ = false;
       }
-      return true;
+      break;
     }
     default:
       if (tapped_window_) {
         Shell::Get()->window_cycle_controller()->SetFocusedWindow(
             tapped_window_);
-        return true;
+        break;
       }
-      break;
+      return;
   }
 
-  return false;
+  event->SetHandled();
+  event->StopPropagation();
+
+  if (should_complete_cycling)
+    Shell::Get()->window_cycle_controller()->CompleteCycling();
 }
 
 bool WindowCycleEventFilter::ProcessEventImpl(int finger_count,
diff --git a/ash/wm/window_cycle/window_cycle_event_filter.h b/ash/wm/window_cycle/window_cycle_event_filter.h
index 2cc7e07..912e80b 100644
--- a/ash/wm/window_cycle/window_cycle_event_filter.h
+++ b/ash/wm/window_cycle/window_cycle_event_filter.h
@@ -88,7 +88,7 @@
 
   // Depending on the properties of |event|, may continuously scroll the window
   // cycle list, move the cycle view's focus ring or complete cycling.
-  bool ProcessGestureEvent(ui::GestureEvent* event);
+  void ProcessGestureEvent(ui::GestureEvent* event);
 
   // Called by ProcessMouseEvent() and OnScrollEvent(). May cycle the window
   // cycle list. Returns true if the event has been handled and should not be
diff --git a/ash/wm/workspace/workspace_window_resizer_unittest.cc b/ash/wm/workspace/workspace_window_resizer_unittest.cc
index 6ce468b..f3d191e 100644
--- a/ash/wm/workspace/workspace_window_resizer_unittest.cc
+++ b/ash/wm/workspace/workspace_window_resizer_unittest.cc
@@ -10,7 +10,6 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/toplevel_window_event_handler.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -2229,20 +2228,4 @@
   window_state = WindowState::Get(window_.get());
   EXPECT_FALSE(window_state->IsMaximized());
 }
-
-// Tests that window destroyed is properly handled during window resize.
-TEST_F(WorkspaceWindowResizerTest, WindowDestroyedDuringResize) {
-  InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTRIGHT);
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
-                                     touch_resize_window_.get());
-  generator.PressTouch();
-  EXPECT_TRUE(WindowState::Get(touch_resize_window_.get())->is_dragged());
-  ToplevelWindowEventHandler* event_handler =
-      Shell::Get()->toplevel_window_event_handler();
-  EXPECT_TRUE(event_handler->is_drag_in_progress());
-
-  touch_resize_window_.reset();
-  EXPECT_FALSE(event_handler->is_drag_in_progress());
-}
-
 }  // namespace ash
diff --git a/base/android/java/src/org/chromium/base/ApplicationStatus.java b/base/android/java/src/org/chromium/base/ApplicationStatus.java
index 4306e356..69421aa 100644
--- a/base/android/java/src/org/chromium/base/ApplicationStatus.java
+++ b/base/android/java/src/org/chromium/base/ApplicationStatus.java
@@ -295,7 +295,7 @@
             }
 
             private void checkCallback(Activity activity) {
-                if (BuildConfig.DCHECK_IS_ON) {
+                if (BuildConfig.ENABLE_ASSERTS) {
                     assert reachesWindowCallback(activity.getWindow().getCallback());
                 }
             }
diff --git a/base/android/java/src/org/chromium/base/ContextUtils.java b/base/android/java/src/org/chromium/base/ContextUtils.java
index 927f84a..072c9136f 100644
--- a/base/android/java/src/org/chromium/base/ContextUtils.java
+++ b/base/android/java/src/org/chromium/base/ContextUtils.java
@@ -111,7 +111,7 @@
     private static void initJavaSideApplicationContext(Context appContext) {
         assert appContext != null;
         // Guard against anyone trying to downcast.
-        if (BuildConfig.DCHECK_IS_ON && appContext instanceof Application) {
+        if (BuildConfig.ENABLE_ASSERTS && appContext instanceof Application) {
             appContext = new ContextWrapper(appContext);
         }
         sApplicationContext = appContext;
diff --git a/base/android/java/src/org/chromium/base/LifetimeAssert.java b/base/android/java/src/org/chromium/base/LifetimeAssert.java
index e723c61..6f7cb70 100644
--- a/base/android/java/src/org/chromium/base/LifetimeAssert.java
+++ b/base/android/java/src/org/chromium/base/LifetimeAssert.java
@@ -122,7 +122,7 @@
     }
 
     public static LifetimeAssert create(Object target) {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return null;
         }
         return new LifetimeAssert(
@@ -130,7 +130,7 @@
     }
 
     public static LifetimeAssert create(Object target, boolean safeToGc) {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return null;
         }
         return new LifetimeAssert(
@@ -138,7 +138,7 @@
     }
 
     public static void setSafeToGc(LifetimeAssert asserter, boolean value) {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             // This guaratees that the target object is reachable until after mSafeToGc value
             // is updated here. See comment on Reference.reachabilityFence and review comments
             // on https://chromium-review.googlesource.com/c/chromium/src/+/1887151 for a
@@ -146,14 +146,14 @@
             // reachabilityFence because robolectric has problems mocking out that method,
             // and this should work for all Android versions.
             synchronized (asserter.mTarget) {
-                // asserter is never null when DCHECK_IS_ON.
+                // asserter is never null when ENABLE_ASSERTS.
                 asserter.mWrapper.mSafeToGc = value;
             }
         }
     }
 
     public static void assertAllInstancesDestroyedForTesting() throws LifetimeAssertException {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return;
         }
         synchronized (WrappedReference.sActiveWrappers) {
diff --git a/base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java b/base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java
index 660b7bc5..77bd3c90 100644
--- a/base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java
+++ b/base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java
@@ -20,7 +20,7 @@
 
     public static void checkLoaded(boolean isMainDex) {
         // Necessary to make sure all of these calls are stripped in release builds.
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         if (sProvider == null) return;
 
diff --git a/base/android/java/src/org/chromium/base/UnownedUserDataKey.java b/base/android/java/src/org/chromium/base/UnownedUserDataKey.java
index 23308c4..c095d10 100644
--- a/base/android/java/src/org/chromium/base/UnownedUserDataKey.java
+++ b/base/android/java/src/org/chromium/base/UnownedUserDataKey.java
@@ -184,7 +184,7 @@
     }
 
     private void assertNoDestroyedAttachments() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             for (UnownedUserDataHost attachedHost : mWeakHostAttachments) {
                 if (attachedHost.isDestroyed()) {
                     assert false : "Host should have been removed already.";
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index 84b00f0..8a0c45d 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -406,9 +406,10 @@
         }
     }
 
-    @CheckDiscard("Can't use @RemovableInRelease because Release build with DCHECK_IS_ON needs it")
+    @CheckDiscard(
+            "Can't use @RemovableInRelease because Release build with ENABLE_ASSERTS needs it")
     public void enableJniChecks() {
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         NativeLibraryLoadedStatus.setProvider(new NativeLibraryLoadedStatusProvider() {
             @Override
diff --git a/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java b/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java
index 101300be..9f7dff3 100644
--- a/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java
+++ b/base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java
@@ -76,11 +76,11 @@
                 blamedClass = field.get(runnable).getClass();
             }
         } catch (NoSuchFieldException e) {
-            if (BuildConfig.DCHECK_IS_ON) {
+            if (BuildConfig.ENABLE_ASSERTS) {
                 throw new RuntimeException(e);
             }
         } catch (IllegalAccessException e) {
-            if (BuildConfig.DCHECK_IS_ON) {
+            if (BuildConfig.ENABLE_ASSERTS) {
                 throw new RuntimeException(e);
             }
         }
diff --git a/base/android/java/templates/BuildConfig.template b/base/android/java/templates/BuildConfig.template
index b40fe5d..3e3af00 100644
--- a/base/android/java/templates/BuildConfig.template
+++ b/base/android/java/templates/BuildConfig.template
@@ -28,10 +28,10 @@
     public static MAYBE_FINAL boolean IS_MULTIDEX_ENABLED MAYBE_FALSE;
 #endif
 
-#if defined(_DCHECK_IS_ON)
-    public static MAYBE_FINAL boolean DCHECK_IS_ON = true;
+#if defined(_ENABLE_ASSERTS)
+    public static MAYBE_FINAL boolean ENABLE_ASSERTS = true;
 #else
-    public static MAYBE_FINAL boolean DCHECK_IS_ON MAYBE_FALSE;
+    public static MAYBE_FINAL boolean ENABLE_ASSERTS MAYBE_FALSE;
 #endif
 
 #if defined(_IS_UBSAN)
diff --git a/base/android/javatests/src/org/chromium/base/AssertsTest.java b/base/android/javatests/src/org/chromium/base/AssertsTest.java
index 530e67b..ae5bb63 100644
--- a/base/android/javatests/src/org/chromium/base/AssertsTest.java
+++ b/base/android/javatests/src/org/chromium/base/AssertsTest.java
@@ -23,16 +23,16 @@
     @SmallTest
     @SuppressWarnings("UseCorrectAssertInTests")
     public void testAssertsWorkAsExpected() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             try {
                 assert false;
             } catch (AssertionError e) {
-                // When DCHECK is on, asserts should throw AssertionErrors.
+                // When asserts are enabled, asserts should throw AssertionErrors.
                 return;
             }
             Assert.fail("Java assert unexpectedly didn't fire.");
         } else {
-            // When DCHECK isn't on, asserts should be removed by proguard.
+            // When asserts are disabled, asserts should be removed by proguard.
             assert false : "Java assert unexpectedly fired.";
         }
     }
diff --git a/base/android/javatests/src/org/chromium/base/IntentUtilsTest.java b/base/android/javatests/src/org/chromium/base/IntentUtilsTest.java
index 65da43d..6e2d180 100644
--- a/base/android/javatests/src/org/chromium/base/IntentUtilsTest.java
+++ b/base/android/javatests/src/org/chromium/base/IntentUtilsTest.java
@@ -43,7 +43,7 @@
             asserted = true;
             if (!expectAssertion) throw e;
         }
-        if (BuildConfig.DCHECK_IS_ON) Assert.assertEquals(expectAssertion, asserted);
+        if (BuildConfig.ENABLE_ASSERTS) Assert.assertEquals(expectAssertion, asserted);
     }
 
     @Test
diff --git a/base/android/javatests/src/org/chromium/base/library_loader/EarlyNativeTest.java b/base/android/javatests/src/org/chromium/base/library_loader/EarlyNativeTest.java
index d0255d8..907b0f5a0 100644
--- a/base/android/javatests/src/org/chromium/base/library_loader/EarlyNativeTest.java
+++ b/base/android/javatests/src/org/chromium/base/library_loader/EarlyNativeTest.java
@@ -126,7 +126,7 @@
     @SmallTest
     public void testNativeMethodsReadyAfterLibraryInitialized() {
         // Test is a no-op if DCHECK isn't on.
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         LibraryLoader.getInstance().enableJniChecks();
 
@@ -152,7 +152,7 @@
     @SmallTest
     public void testNativeMethodsNotReadyThrows() {
         // Test is a no-op if dcheck isn't on.
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         LibraryLoader.getInstance().enableJniChecks();
 
diff --git a/base/android/junit/src/org/chromium/base/LifetimeAssertTest.java b/base/android/junit/src/org/chromium/base/LifetimeAssertTest.java
index b29b1bc..f2df4dd 100644
--- a/base/android/junit/src/org/chromium/base/LifetimeAssertTest.java
+++ b/base/android/junit/src/org/chromium/base/LifetimeAssertTest.java
@@ -32,7 +32,7 @@
 
     @Before
     public void setUp() {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return;
         }
         mTestClass = new TestClass();
@@ -52,14 +52,14 @@
 
     @After
     public void tearDown() {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return;
         }
         LifetimeAssert.sTestHook = null;
     }
 
     private void runTest(boolean setSafe) {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return;
         }
 
@@ -98,7 +98,7 @@
 
     @Test
     public void testAssertAllInstancesDestroyedForTesting() {
-        if (!BuildConfig.DCHECK_IS_ON) {
+        if (!BuildConfig.ENABLE_ASSERTS) {
             return;
         }
         try {
diff --git a/base/android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java b/base/android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java
index 2992142ca..aee1662 100644
--- a/base/android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java
+++ b/base/android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java
@@ -918,7 +918,7 @@
 
     private void assertAsserts(Runnable runnable) {
         // When DCHECK is off, asserts are stripped.
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         try {
             runnable.run();
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index 6e36e3f..fbdea9f1 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -36,10 +36,6 @@
 #include "base/values.h"
 #include "build/build_config.h"
 
-namespace {
-constexpr char kAsciiNewLine[] = "\n";
-}  // namespace
-
 namespace base {
 
 namespace {
@@ -576,27 +572,9 @@
   return unlogged_samples_->AddFromPickle(iter);
 }
 
-void Histogram::WriteAscii(std::string* output) const {
-  // Get local (stack) copies of all effectively volatile class data so that we
-  // are consistent across our output activities.
-  std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
-  WriteAsciiHeader(*snapshot, output);
-  output->append(kAsciiNewLine);
-  WriteAsciiBody(*snapshot, true, kAsciiNewLine, output);
-}
-
 base::DictionaryValue Histogram::ToGraphDict() const {
   std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
-  std::string header;
-  std::string body;
-  base::DictionaryValue dict;
-
-  WriteAsciiHeader(*snapshot, &header);
-  WriteAsciiBody(*snapshot, true, kAsciiNewLine, &body);
-  dict.SetString("header", header);
-  dict.SetString("body", body);
-
-  return dict;
+  return snapshot->ToGraphDict(histogram_name(), flags());
 }
 
 void Histogram::ValidateHistogramContents() const {
@@ -646,10 +624,6 @@
 
 Histogram::~Histogram() = default;
 
-bool Histogram::PrintEmptyBucket(uint32_t index) const {
-  return true;
-}
-
 const std::string Histogram::GetAsciiBucketRange(uint32_t i) const {
   return GetSimpleAsciiBucketRange(ranges(i));
 }
@@ -697,112 +671,6 @@
   return samples;
 }
 
-void Histogram::WriteAsciiBody(const SampleVector& snapshot,
-                               bool graph_it,
-                               const std::string& newline,
-                               std::string* output) const {
-  Count sample_count = snapshot.TotalCount();
-
-  // Prepare to normalize graphical rendering of bucket contents.
-  double max_size = 0;
-  double scaling_factor = 1;
-  if (graph_it)
-    max_size = GetPeakBucketSize(snapshot);
-  // Scale histogram bucket counts to take at most 72 characters.
-  // Note: Keep in sync w/ kLineLength sparse_histogram.cc
-  const double kLineLength = 72;
-  if (max_size > kLineLength)
-    scaling_factor = kLineLength / max_size;
-
-  // Calculate space needed to print bucket range numbers.  Leave room to print
-  // nearly the largest bucket range without sliding over the histogram.
-  uint32_t largest_non_empty_bucket = bucket_count() - 1;
-  while (0 == snapshot.GetCountAtIndex(largest_non_empty_bucket)) {
-    if (0 == largest_non_empty_bucket)
-      break;  // All buckets are empty.
-    --largest_non_empty_bucket;
-  }
-
-  // Calculate largest print width needed for any of our bucket range displays.
-  size_t print_width = 1;
-  for (uint32_t i = 0; i < bucket_count(); ++i) {
-    if (snapshot.GetCountAtIndex(i)) {
-      size_t width = GetAsciiBucketRange(i).size() + 1;
-      if (width > print_width)
-        print_width = width;
-    }
-  }
-
-  int64_t remaining = sample_count;
-  int64_t past = 0;
-  // Output the actual histogram graph.
-  for (uint32_t i = 0; i < bucket_count(); ++i) {
-    Count current = snapshot.GetCountAtIndex(i);
-    if (!current && !PrintEmptyBucket(i))
-      continue;
-    remaining -= current;
-    std::string range = GetAsciiBucketRange(i);
-    output->append(range);
-    for (size_t j = 0; range.size() + j < print_width + 1; ++j)
-      output->push_back(' ');
-    if (0 == current && i < bucket_count() - 1 &&
-        0 == snapshot.GetCountAtIndex(i + 1)) {
-      while (i < bucket_count() - 1 && 0 == snapshot.GetCountAtIndex(i + 1)) {
-        ++i;
-      }
-      output->append("... ");
-      output->append(newline);
-      continue;  // No reason to plot emptiness.
-    }
-    Count current_size = round(current * scaling_factor);
-    if (graph_it)
-      WriteAsciiBucketGraph(current_size, kLineLength, output);
-    WriteAsciiBucketContext(past, current, remaining, i, output);
-    output->append(newline);
-    past += current;
-  }
-  DCHECK_EQ(sample_count, past);
-}
-
-double Histogram::GetPeakBucketSize(const SampleVectorBase& samples) const {
-  Count max = 0;
-  for (uint32_t i = 0; i < bucket_count() ; ++i) {
-    Count current = samples.GetCountAtIndex(i);
-    if (current > max)
-      max = current;
-  }
-  return max;
-}
-
-void Histogram::WriteAsciiHeader(const SampleVectorBase& samples,
-                                 std::string* output) const {
-  Count sample_count = samples.TotalCount();
-
-  StringAppendF(output, "Histogram: %s recorded %d samples", histogram_name(),
-                sample_count);
-  if (sample_count == 0) {
-    DCHECK_EQ(samples.sum(), 0);
-  } else {
-    double mean = static_cast<float>(samples.sum()) / sample_count;
-    StringAppendF(output, ", mean = %.1f", mean);
-  }
-  if (flags())
-    StringAppendF(output, " (flags = 0x%x)", flags());
-}
-
-void Histogram::WriteAsciiBucketContext(const int64_t past,
-                                        const Count current,
-                                        const int64_t remaining,
-                                        const uint32_t i,
-                                        std::string* output) const {
-  double scaled_sum = (past + current + remaining) / 100.0;
-  WriteAsciiBucketValue(current, scaled_sum, output);
-  if (0 < i) {
-    double percentage = past / scaled_sum;
-    StringAppendF(output, " {%3.1f%%}", percentage);
-  }
-}
-
 void Histogram::GetParameters(DictionaryValue* params) const {
   params->SetString("type", HistogramTypeToString(GetHistogramType()));
   params->SetIntKey("min", declared_min());
@@ -980,10 +848,6 @@
   return it->second;
 }
 
-bool LinearHistogram::PrintEmptyBucket(uint32_t index) const {
-  return bucket_description_.find(ranges(index)) == bucket_description_.end();
-}
-
 // static
 void LinearHistogram::InitializeBucketRanges(Sample minimum,
                                              Sample maximum,
diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h
index c43a80c..496d2e9 100644
--- a/base/metrics/histogram.h
+++ b/base/metrics/histogram.h
@@ -218,7 +218,6 @@
   std::unique_ptr<HistogramSamples> SnapshotFinalDelta() const override;
   void AddSamples(const HistogramSamples& samples) override;
   bool AddSamplesFromPickle(base::PickleIterator* iter) override;
-  void WriteAscii(std::string* output) const override;
   base::DictionaryValue ToGraphDict() const override;
 
   // Validates the histogram contents and CHECKs on errors.
@@ -258,9 +257,6 @@
   // HistogramBase implementation:
   void SerializeInfoImpl(base::Pickle* pickle) const override;
 
-  // Method to override to skip the display of the i'th bucket if it's empty.
-  virtual bool PrintEmptyBucket(uint32_t index) const;
-
   // Return a string description of what goes in a given bucket.
   // Most commonly this is the numeric value, but in derived classes it may
   // be a name (or string description) given to the bucket.
@@ -272,7 +268,6 @@
   FRIEND_TEST_ALL_PREFIXES(HistogramTest, BoundsTest);
   FRIEND_TEST_ALL_PREFIXES(HistogramTest, BucketPlacementTest);
   FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts);
-  FRIEND_TEST_ALL_PREFIXES(HistogramTest, GetPeakBucketSize);
 
   friend class StatisticsRecorder;  // To allow it to delete duplicates.
   friend class StatisticsRecorderTest;
@@ -289,29 +284,6 @@
   // Create a copy of unlogged samples.
   std::unique_ptr<SampleVector> SnapshotUnloggedSamples() const;
 
-  //----------------------------------------------------------------------------
-  // Helpers for emitting Ascii graphic.  Each method appends data to output.
-
-  void WriteAsciiBody(const SampleVector& snapshot,
-                      bool graph_it,
-                      const std::string& newline,
-                      std::string* output) const;
-
-  // Find out how large (graphically) the largest bucket will appear to be.
-  double GetPeakBucketSize(const SampleVectorBase& samples) const;
-
-  // Write a common header message describing this histogram.
-  void WriteAsciiHeader(const SampleVectorBase& samples,
-                        std::string* output) const;
-
-  // Write information about previous, current, and next buckets.
-  // Information such as cumulative percentage, etc.
-  void WriteAsciiBucketContext(const int64_t past,
-                               const Count current,
-                               const int64_t remaining,
-                               const uint32_t i,
-                               std::string* output) const;
-
   // Writes the type, min, max, and bucket count information of the histogram in
   // |params|.
   void GetParameters(DictionaryValue* params) const override;
@@ -423,10 +395,6 @@
   // let parent class provide a (numeric) description.
   const std::string GetAsciiBucketRange(uint32_t i) const override;
 
-  // Skip printing of name for numeric range if we have a name (and if this is
-  // an empty bucket).
-  bool PrintEmptyBucket(uint32_t index) const override;
-
  private:
   friend BASE_EXPORT HistogramBase* DeserializeHistogramInfo(
       base::PickleIterator* iter);
diff --git a/base/metrics/histogram_base.cc b/base/metrics/histogram_base.cc
index 42931fd..69e9eb0 100644
--- a/base/metrics/histogram_base.cc
+++ b/base/metrics/histogram_base.cc
@@ -226,6 +226,13 @@
   StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum);
 }
 
+void HistogramBase::WriteAscii(std::string* output) const {
+  base::DictionaryValue graph_dict = ToGraphDict();
+  output->append(*graph_dict.FindStringKey("header"));
+  output->append("\n");
+  output->append(*graph_dict.FindStringKey("body"));
+}
+
 // static
 char const* HistogramBase::GetPermanentName(const std::string& name) {
   // A set of histogram names that provides the "permanent" lifetime required
diff --git a/base/metrics/histogram_base.h b/base/metrics/histogram_base.h
index 5f15a39..e3eba0f 100644
--- a/base/metrics/histogram_base.h
+++ b/base/metrics/histogram_base.h
@@ -248,7 +248,7 @@
   virtual std::unique_ptr<HistogramSamples> SnapshotFinalDelta() const = 0;
 
   // The following method provides graphical histogram displays.
-  virtual void WriteAscii(std::string* output) const = 0;
+  virtual void WriteAscii(std::string* output) const;
 
   // Returns histograms data as a Dict (or an empty dict if not available),
   // with the following format:
diff --git a/base/metrics/histogram_base_unittest.cc b/base/metrics/histogram_base_unittest.cc
index d89e99e..5eded44 100644
--- a/base/metrics/histogram_base_unittest.cc
+++ b/base/metrics/histogram_base_unittest.cc
@@ -274,28 +274,4 @@
   EXPECT_EQ(add_count, samples->GetCount(0));
 }
 
-// Tests GetPeakBucketSize() returns accurate max bucket size.
-TEST(HistogramTest, GetPeakBucketSize) {
-  Histogram* histogram = static_cast<Histogram*>(
-      Histogram::FactoryGet("Histogram",
-                            /*minimum=*/1,
-                            /*maximum=*/64,
-                            /*bucket_count=*/8,
-                            /*flags=*/HistogramBase::kNoFlags));
-
-  // Add 1 sample to 0th bucket; 2 to 6th; 3 to 313th.
-  for (int i = 0; i < 1; i++) {
-    histogram->Add(0);
-  }
-  for (int i = 0; i < 2; i++) {
-    histogram->Add(6);
-  }
-  for (int i = 0; i < 3; i++) {
-    histogram->Add(313);
-  }
-
-  // The largest bucket size should be 3.
-  EXPECT_EQ(3, histogram->GetPeakBucketSize(*histogram->SnapshotAllSamples()));
-}
-
 }  // namespace base
diff --git a/base/metrics/histogram_samples.cc b/base/metrics/histogram_samples.cc
index 6830637..4e6b3c0 100644
--- a/base/metrics/histogram_samples.cc
+++ b/base/metrics/histogram_samples.cc
@@ -12,6 +12,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/numerics/safe_math.h"
 #include "base/pickle.h"
+#include "base/strings/stringprintf.h"
 
 namespace base {
 
@@ -263,6 +264,101 @@
                      static_cast<int32_t>(id()));
 }
 
+base::DictionaryValue HistogramSamples::ToGraphDict(StringPiece histogram_name,
+                                                    int32_t flags) const {
+  base::DictionaryValue dict;
+  dict.SetString("header", GetAsciiHeader(histogram_name, flags));
+  dict.SetString("body", GetAsciiBody());
+  return dict;
+}
+
+std::string HistogramSamples::GetAsciiHeader(StringPiece histogram_name,
+                                             int32_t flags) const {
+  std::string output;
+  StringAppendF(&output, "Histogram: %.*s recorded %d samples",
+                static_cast<int>(histogram_name.size()), histogram_name.data(),
+                TotalCount());
+  if (flags)
+    StringAppendF(&output, " (flags = 0x%x)", flags);
+  return output;
+}
+
+std::string HistogramSamples::GetAsciiBody() const {
+  HistogramBase::Count total_count = TotalCount();
+  double scaled_total_count = total_count / 100.0;
+
+  // Determine how wide the largest bucket range is (how many digits to print),
+  // so that we'll be able to right-align starts for the graphical bars.
+  // Determine which bucket has the largest sample count so that we can
+  // normalize the graphical bar-width relative to that sample count.
+  HistogramBase::Count largest_count = 0;
+  HistogramBase::Sample largest_sample = 0;
+  std::unique_ptr<SampleCountIterator> it = Iterator();
+  while (!it->Done()) {
+    HistogramBase::Sample min;
+    int64_t max;
+    HistogramBase::Count count;
+    it->Get(&min, &max, &count);
+    if (min > largest_sample)
+      largest_sample = min;
+    if (count > largest_count)
+      largest_count = count;
+    it->Next();
+  }
+  // Scale histogram bucket counts to take at most 72 characters.
+  // Note: Keep in sync w/ kLineLength sample_vector.cc
+  const double kLineLength = 72;
+  double scaling_factor = 1;
+  if (largest_count > kLineLength)
+    scaling_factor = kLineLength / largest_count;
+  size_t print_width = GetSimpleAsciiBucketRange(largest_sample).size() + 1;
+
+  // iterate over each item and display them
+  it = Iterator();
+  std::string output;
+  while (!it->Done()) {
+    HistogramBase::Sample min;
+    int64_t max;
+    HistogramBase::Count count;
+    it->Get(&min, &max, &count);
+
+    // value is min, so display it
+    std::string range = GetSimpleAsciiBucketRange(min);
+    output.append(range);
+    for (size_t j = 0; range.size() + j < print_width + 1; ++j)
+      output.push_back(' ');
+    HistogramBase::Count current_size = round(count * scaling_factor);
+    WriteAsciiBucketGraph(current_size, kLineLength, &output);
+    WriteAsciiBucketValue(count, scaled_total_count, &output);
+    StringAppendF(&output, "\n");
+    it->Next();
+  }
+  return output;
+}
+
+void HistogramSamples::WriteAsciiBucketGraph(double x_count,
+                                             int line_length,
+                                             std::string* output) const {
+  int x_remainder = line_length - x_count;
+
+  while (0 < x_count--)
+    output->append("-");
+  output->append("O");
+  while (0 < x_remainder--)
+    output->append(" ");
+}
+
+void HistogramSamples::WriteAsciiBucketValue(HistogramBase::Count current,
+                                             double scaled_sum,
+                                             std::string* output) const {
+  StringAppendF(output, " (%d = %3.1f%%)", current, current / scaled_sum);
+}
+
+const std::string HistogramSamples::GetSimpleAsciiBucketRange(
+    HistogramBase::Sample sample) const {
+  return StringPrintf("%d", sample);
+}
+
 SampleCountIterator::~SampleCountIterator() = default;
 
 bool SampleCountIterator::GetBucketIndex(size_t* index) const {
diff --git a/base/metrics/histogram_samples.h b/base/metrics/histogram_samples.h
index d78b449..3e08bf6 100644
--- a/base/metrics/histogram_samples.h
+++ b/base/metrics/histogram_samples.h
@@ -147,7 +147,13 @@
   virtual std::unique_ptr<SampleCountIterator> Iterator() const = 0;
   virtual void Serialize(Pickle* pickle) const;
 
-  // Accessor fuctions.
+  // Returns ASCII representation of histograms data for histogram samples.
+  // The dictionary returned will be of the form
+  // {"header":<string>, "body": <string>}
+  base::DictionaryValue ToGraphDict(StringPiece histogram_name,
+                                    int32_t flags) const;
+
+  // Accessor functions.
   uint64_t id() const { return meta_->id; }
   int64_t sum() const {
 #ifdef ARCH_CPU_64_BITS
@@ -197,6 +203,28 @@
     return meta_->single_sample;
   }
 
+  // Produces an actual graph (set of blank vs non blank char's) for a bucket.
+  void WriteAsciiBucketGraph(double x_count,
+                             int line_length,
+                             std::string* output) const;
+
+  // Writes textual description of the bucket contents (relative to histogram).
+  // Output is the count in the buckets, as well as the percentage.
+  void WriteAsciiBucketValue(HistogramBase::Count current,
+                             double scaled_sum,
+                             std::string* output) const;
+
+  // Gets a body for this histogram samples.
+  virtual std::string GetAsciiBody() const;
+
+  // Gets a header message describing this histogram samples.
+  virtual std::string GetAsciiHeader(StringPiece histogram_name,
+                                     int32_t flags) const;
+
+  // Returns a string description of what goes in a given bucket.
+  const std::string GetSimpleAsciiBucketRange(
+      HistogramBase::Sample sample) const;
+
   Metadata* meta() { return meta_; }
 
  private:
diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc
index ce1ea73..dd91695 100644
--- a/base/metrics/sample_vector.cc
+++ b/base/metrics/sample_vector.cc
@@ -10,6 +10,7 @@
 #include "base/metrics/persistent_memory_allocator.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/platform_thread.h"
 
@@ -313,6 +314,108 @@
   return counts() != nullptr;
 }
 
+std::string SampleVector::GetAsciiHeader(StringPiece histogram_name,
+                                         int32_t flags) const {
+  Count sample_count = TotalCount();
+  std::string output;
+  StringAppendF(&output, "Histogram: %.*s recorded %d samples",
+                static_cast<int>(histogram_name.size()), histogram_name.data(),
+                sample_count);
+  if (sample_count == 0) {
+    DCHECK_EQ(sum(), 0);
+  } else {
+    double mean = static_cast<float>(sum()) / sample_count;
+    StringAppendF(&output, ", mean = %.1f", mean);
+  }
+  if (flags)
+    StringAppendF(&output, " (flags = 0x%x)", flags);
+  return output;
+}
+
+std::string SampleVector::GetAsciiBody() const {
+  Count sample_count = TotalCount();
+
+  // Prepare to normalize graphical rendering of bucket contents.
+  double max_size = 0;
+  double scaling_factor = 1;
+  max_size = GetPeakBucketSize();
+  // Scale histogram bucket counts to take at most 72 characters.
+  // Note: Keep in sync w/ kLineLength histogram_samples.cc
+  const double kLineLength = 72;
+  if (max_size > kLineLength)
+    scaling_factor = kLineLength / max_size;
+
+  // Calculate space needed to print bucket range numbers.  Leave room to print
+  // nearly the largest bucket range without sliding over the histogram.
+  uint32_t largest_non_empty_bucket = bucket_count() - 1;
+  while (0 == GetCountAtIndex(largest_non_empty_bucket)) {
+    if (0 == largest_non_empty_bucket)
+      break;  // All buckets are empty.
+    --largest_non_empty_bucket;
+  }
+
+  // Calculate largest print width needed for any of our bucket range displays.
+  size_t print_width = 1;
+  for (uint32_t i = 0; i < bucket_count(); ++i) {
+    if (GetCountAtIndex(i)) {
+      size_t width =
+          GetSimpleAsciiBucketRange(bucket_ranges()->range(i)).size() + 1;
+      if (width > print_width)
+        print_width = width;
+    }
+  }
+
+  int64_t remaining = sample_count;
+  int64_t past = 0;
+  std::string output;
+  // Output the actual histogram graph.
+  for (uint32_t i = 0; i < bucket_count(); ++i) {
+    Count current = GetCountAtIndex(i);
+    remaining -= current;
+    std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i));
+    output.append(range);
+    for (size_t j = 0; range.size() + j < print_width + 1; ++j)
+      output.push_back(' ');
+    if (0 == current && i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
+      while (i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
+        ++i;
+      }
+      output.append("... \n");
+      continue;  // No reason to plot emptiness.
+    }
+    Count current_size = round(current * scaling_factor);
+    WriteAsciiBucketGraph(current_size, kLineLength, &output);
+    WriteAsciiBucketContext(past, current, remaining, i, &output);
+    output.append("\n");
+    past += current;
+  }
+  DCHECK_EQ(sample_count, past);
+  return output;
+}
+
+double SampleVector::GetPeakBucketSize() const {
+  Count max = 0;
+  for (uint32_t i = 0; i < bucket_count(); ++i) {
+    Count current = GetCountAtIndex(i);
+    if (current > max)
+      max = current;
+  }
+  return max;
+}
+
+void SampleVector::WriteAsciiBucketContext(int64_t past,
+                                           Count current,
+                                           int64_t remaining,
+                                           uint32_t current_bucket_index,
+                                           std::string* output) const {
+  double scaled_sum = (past + current + remaining) / 100.0;
+  WriteAsciiBucketValue(current, scaled_sum, output);
+  if (0 < current_bucket_index) {
+    double percentage = past / scaled_sum;
+    StringAppendF(output, " {%3.1f%%}", percentage);
+  }
+}
+
 HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() {
   local_counts_.resize(counts_size());
   return &local_counts_[0];
diff --git a/base/metrics/sample_vector.h b/base/metrics/sample_vector.h
index 0765e973..50fc406 100644
--- a/base/metrics/sample_vector.h
+++ b/base/metrics/sample_vector.h
@@ -120,10 +120,30 @@
   ~SampleVector() override;
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(SampleVectorTest, GetPeakBucketSize);
+
+  // HistogramSamples:
+  std::string GetAsciiBody() const override;
+  std::string GetAsciiHeader(StringPiece histogram_name,
+                             int32_t flags) const override;
+
   // SampleVectorBase:
   bool MountExistingCountsStorage() const override;
   HistogramBase::Count* CreateCountsStorageWhileLocked() override;
 
+  // Writes cumulative percentage information based on the number
+  // of past, current, and remaining bucket samples.
+  void WriteAsciiBucketContext(int64_t past,
+                               HistogramBase::Count current,
+                               int64_t remaining,
+                               uint32_t current_bucket_index,
+                               std::string* output) const;
+
+  // Finds out how large (graphically) the largest bucket will appear to be.
+  double GetPeakBucketSize() const;
+
+  size_t bucket_count() const { return bucket_ranges()->bucket_count(); }
+
   // Simple local storage for counts.
   mutable std::vector<HistogramBase::AtomicCount> local_counts_;
 };
diff --git a/base/metrics/sample_vector_unittest.cc b/base/metrics/sample_vector_unittest.cc
index 0c52bad..0a8cbb3 100644
--- a/base/metrics/sample_vector_unittest.cc
+++ b/base/metrics/sample_vector_unittest.cc
@@ -541,4 +541,19 @@
   EXPECT_EQ(200, samples2.GetCount(8));
 }
 
+// Tests GetPeakBucketSize() returns accurate max bucket size.
+TEST_F(SampleVectorTest, GetPeakBucketSize) {
+  // Custom buckets: [1, 5) [5, 10) [10, 20)
+  BucketRanges ranges(4);
+  ranges.set_range(0, 1);
+  ranges.set_range(1, 5);
+  ranges.set_range(2, 10);
+  ranges.set_range(3, 20);
+  SampleVector samples(1, &ranges);
+  samples.Accumulate(3, 1);
+  samples.Accumulate(6, 2);
+  samples.Accumulate(12, 3);
+  EXPECT_EQ(3, samples.GetPeakBucketSize());
+}
+
 }  // namespace base
diff --git a/base/metrics/sparse_histogram.cc b/base/metrics/sparse_histogram.cc
index ad34fc2..d9583ebe 100644
--- a/base/metrics/sparse_histogram.cc
+++ b/base/metrics/sparse_histogram.cc
@@ -20,10 +20,6 @@
 #include "base/synchronization/lock.h"
 #include "base/values.h"
 
-namespace {
-constexpr char kAsciiNewLine[] = "\n";
-}  // namespace
-
 namespace base {
 
 typedef HistogramBase::Count Count;
@@ -170,27 +166,9 @@
   return unlogged_samples_->AddFromPickle(iter);
 }
 
-void SparseHistogram::WriteAscii(std::string* output) const {
-  // Get a local copy of the data so we are consistent.
-  std::unique_ptr<HistogramSamples> snapshot = SnapshotSamples();
-
-  WriteAsciiHeader(*snapshot, output);
-  output->append(kAsciiNewLine);
-  WriteAsciiBody(*snapshot, true, kAsciiNewLine, output);
-}
-
 base::DictionaryValue SparseHistogram::ToGraphDict() const {
   std::unique_ptr<HistogramSamples> snapshot = SnapshotSamples();
-  std::string header;
-  std::string body;
-  base::DictionaryValue dict;
-
-  WriteAsciiHeader(*snapshot, &header);
-  WriteAsciiBody(*snapshot, true, kAsciiNewLine, &body);
-  dict.SetString("header", header);
-  dict.SetString("body", body);
-
-  return dict;
+  return snapshot->ToGraphDict(histogram_name(), flags());
 }
 
 void SparseHistogram::SerializeInfoImpl(Pickle* pickle) const {
@@ -243,67 +221,4 @@
   params->SetString("type", HistogramTypeToString(GetHistogramType()));
 }
 
-void SparseHistogram::WriteAsciiBody(const HistogramSamples& snapshot,
-                                     bool graph_it,
-                                     const std::string& newline,
-                                     std::string* output) const {
-  Count total_count = snapshot.TotalCount();
-  double scaled_total_count = total_count / 100.0;
-
-  // Determine how wide the largest bucket range is (how many digits to print),
-  // so that we'll be able to right-align starts for the graphical bars.
-  // Determine which bucket has the largest sample count so that we can
-  // normalize the graphical bar-width relative to that sample count.
-  Count largest_count = 0;
-  Sample largest_sample = 0;
-  std::unique_ptr<SampleCountIterator> it = snapshot.Iterator();
-  while (!it->Done()) {
-    Sample min;
-    int64_t max;
-    Count count;
-    it->Get(&min, &max, &count);
-    if (min > largest_sample)
-      largest_sample = min;
-    if (count > largest_count)
-      largest_count = count;
-    it->Next();
-  }
-  // Scale histogram bucket counts to take at most 72 characters.
-  // Note: Keep in sync w/ kLineLength histogram.cc
-  const double kLineLength = 72;
-  double scaling_factor = 1;
-  if (largest_count > kLineLength)
-    scaling_factor = kLineLength / largest_count;
-  size_t print_width = GetSimpleAsciiBucketRange(largest_sample).size() + 1;
-
-  // iterate over each item and display them
-  it = snapshot.Iterator();
-  while (!it->Done()) {
-    Sample min;
-    int64_t max;
-    Count count;
-    it->Get(&min, &max, &count);
-
-    // value is min, so display it
-    std::string range = GetSimpleAsciiBucketRange(min);
-    output->append(range);
-    for (size_t j = 0; range.size() + j < print_width + 1; ++j)
-      output->push_back(' ');
-    Count current_size = round(count * scaling_factor);
-    if (graph_it)
-      WriteAsciiBucketGraph(current_size, kLineLength, output);
-    WriteAsciiBucketValue(count, scaled_total_count, output);
-    output->append(newline);
-    it->Next();
-  }
-}
-
-void SparseHistogram::WriteAsciiHeader(const HistogramSamples& snapshot,
-                                       std::string* output) const {
-  StringAppendF(output, "Histogram: %s recorded %d samples", histogram_name(),
-                snapshot.TotalCount());
-  if (flags())
-    StringAppendF(output, " (flags = 0x%x)", flags());
-}
-
 }  // namespace base
diff --git a/base/metrics/sparse_histogram.h b/base/metrics/sparse_histogram.h
index ff1ce56..f298b3c 100644
--- a/base/metrics/sparse_histogram.h
+++ b/base/metrics/sparse_histogram.h
@@ -55,7 +55,6 @@
   std::unique_ptr<HistogramSamples> SnapshotSamples() const override;
   std::unique_ptr<HistogramSamples> SnapshotDelta() override;
   std::unique_ptr<HistogramSamples> SnapshotFinalDelta() const override;
-  void WriteAscii(std::string* output) const override;
   base::DictionaryValue ToGraphDict() const override;
 
  protected:
@@ -78,16 +77,6 @@
   // Writes the type of the sparse histogram in the |params|.
   void GetParameters(DictionaryValue* params) const override;
 
-  // Helpers for emitting Ascii graphic.  Each method appends data to output.
-  void WriteAsciiBody(const HistogramSamples& snapshot,
-                      bool graph_it,
-                      const std::string& newline,
-                      std::string* output) const;
-
-  // Write a common header message describing this histogram.
-  void WriteAsciiHeader(const HistogramSamples& snapshot,
-                        std::string* output) const;
-
   // For constructor calling.
   friend class SparseHistogramTest;
 
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 85bc8ead..652ea74 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -9,6 +9,7 @@
 # toolchains. Checking |is_android| here would therefore be too restrictive.
 if (is_android || is_chromeos) {
   import("//build/config/chromecast_build.gni")
+  import("//build/config/dcheck_always_on.gni")
   import("//build_overrides/build.gni")
   import("abi.gni")
 
@@ -262,6 +263,9 @@
       android_sdk_platform_version = android_sdk_version
     }
 
+    # Whether java assertions and Preconditions checks are enabled.
+    enable_java_asserts = is_java_debug || dcheck_always_on
+
     # Reduce build time by using d8 incremental build.
     enable_incremental_d8 = true
 
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 6183f39..0d2a4ba6 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -9,7 +9,6 @@
 import("//build/config/compiler/compiler.gni")
 import("//build/config/compute_inputs_for_analyze.gni")
 import("//build/config/coverage/coverage.gni")
-import("//build/config/dcheck_always_on.gni")
 import("//build/config/python.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/toolchain/goma.gni")
@@ -1265,8 +1264,7 @@
       _args += [ "--show-desugar-default-interface-warnings" ]
     }
 
-    _enable_assert = is_java_debug || dcheck_always_on
-    if (_enable_assert) {
+    if (enable_java_asserts) {
       # The default for generating dex file format is
       # --force-disable-assertions.
       _args += [ "--force-enable-assertions" ]
@@ -1696,8 +1694,7 @@
           not_needed(invoker, [ "desugar_jars_paths" ])
         }
 
-        _enable_assert = is_java_debug || dcheck_always_on
-        if (_enable_assert) {
+        if (enable_java_asserts) {
           # The default for generating dex file format is
           # --force-disable-assertions.
           args += [ "--force-enable-assertions" ]
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 6d46b5d4..07641a2 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -11,7 +11,6 @@
 import("//build/config/clang/clang.gni")
 import("//build/config/compiler/compiler.gni")
 import("//build/config/coverage/coverage.gni")
-import("//build/config/dcheck_always_on.gni")
 import("//build/config/python.gni")
 import("//build/config/zip.gni")
 import("//build/toolchain/toolchain.gni")
@@ -1920,8 +1919,8 @@
 
       # Set these even when !use_final_fields so that they have correct default
       # values within junit_binary(), which ignores jar_excluded_patterns.
-      if (is_java_debug || dcheck_always_on) {
-        defines += [ "_DCHECK_IS_ON" ]
+      if (enable_java_asserts) {
+        defines += [ "_ENABLE_ASSERTS" ]
       }
       if (use_cfi_diag || is_ubsan || is_ubsan_security || is_ubsan_vptr) {
         defines += [ "_IS_UBSAN" ]
@@ -2825,7 +2824,7 @@
         if (_enable_main_dex_list) {
           proguard_configs += [ "//build/android/multidex.flags" ]
         }
-        if (!dcheck_always_on && (!defined(testonly) || !testonly) &&
+        if (!enable_java_asserts && (!defined(testonly) || !testonly) &&
             # Injected JaCoCo code causes -checkdiscards to fail.
             !use_jacoco_coverage) {
           proguard_configs += [ "//build/android/dcheck_is_off.flags" ]
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index f2ca277e..cc75a47 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-3.20210315.0.1
+3.20210315.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 922620dd..cc75a47 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-3.20210312.3.1
+3.20210315.1.1
diff --git a/cc/base/features.cc b/cc/base/features.cc
index e094789..3494bb7f 100644
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -35,34 +35,18 @@
     base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
-#if !defined(OS_ANDROID)
-// Enables latency recovery on the impl thread.
-const base::Feature kImplLatencyRecovery = {"ImplLatencyRecovery",
-                                            base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Enables latency recovery on the main thread.
-const base::Feature kMainLatencyRecovery = {"MainLatencyRecovery",
-                                            base::FEATURE_DISABLED_BY_DEFAULT};
-#endif  // !defined(OS_ANDROID)
-
 bool IsImplLatencyRecoveryEnabled() {
-#if defined(OS_ANDROID)
-  // TODO(crbug.com/933846): LatencyRecovery is causing jank on Android. Disable
-  // for now, with plan to disable more widely on all platforms.
+  // TODO(crbug.com/1142598): Latency recovery has been disabled by default
+  // since M87. For now, only the flag is removed. If all goes well, remove the
+  // code supporting latency recovery.
   return false;
-#else
-  return base::FeatureList::IsEnabled(kImplLatencyRecovery);
-#endif
 }
 
 bool IsMainLatencyRecoveryEnabled() {
-#if defined(OS_ANDROID)
-  // TODO(crbug.com/933846): LatencyRecovery is causing jank on Android. Disable
-  // for now, with plan to disable more widely on all platforms.
+  // TODO(crbug.com/1142598): Latency recovery has been disabled by default
+  // since M87. For now, only the flag is removed. If all goes well, remove the
+  // code supporting latency recovery.
   return false;
-#else
-  return base::FeatureList::IsEnabled(kMainLatencyRecovery);
-#endif
 }
 
 const base::Feature kRemoveMobileViewportDoubleTap{
diff --git a/cc/base/features.h b/cc/base/features.h
index 5b76f8d..6068e0b 100644
--- a/cc/base/features.h
+++ b/cc/base/features.h
@@ -15,11 +15,6 @@
 CC_BASE_EXPORT extern const base::Feature kImpulseScrollAnimations;
 CC_BASE_EXPORT extern const base::Feature kSynchronizedScrolling;
 
-#if !defined(OS_ANDROID)
-CC_BASE_EXPORT extern const base::Feature kImplLatencyRecovery;
-CC_BASE_EXPORT extern const base::Feature kMainLatencyRecovery;
-#endif  // !defined(OS_ANDROID)
-
 CC_BASE_EXPORT bool IsImplLatencyRecoveryEnabled();
 CC_BASE_EXPORT bool IsMainLatencyRecoveryEnabled();
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 511d6d2..adc0641 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=91
 MINOR=0
-BUILD=4448
+BUILD=4449
 PATCH=0
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index 9392f63..e8c3d71 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -973,7 +973,7 @@
         "force-fieldtrial-params=Study.Group:start_surface_variation/single"})
     public void testNoGURLPreNative() {
         // clang-format on
-        if (!BuildConfig.DCHECK_IS_ON) return;
+        if (!BuildConfig.ENABLE_ASSERTS) return;
 
         collector.checkThat(StartSurfaceConfiguration.isStartSurfaceSinglePaneEnabled(), is(true));
         collector.checkThat(TextUtils.isEmpty(HomepageManager.getHomepageUri()), is(false));
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java
index d63725a..6ed66c2 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java
@@ -25,7 +25,7 @@
     @SmallTest
     @SuppressWarnings("UseCorrectAssertInTests")
     public void assertInTests() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             try {
                 assert false;
             } catch (AssertionError e) {
@@ -43,7 +43,7 @@
     @SmallTest
     @SuppressWarnings("UseCorrectAssertInTests")
     public void assertInModule() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             try {
                 TabGroupUtils.triggerAssertionForTesting();
             } catch (AssertionError e) {
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
index 777e3aa..82031ee3 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
@@ -35,6 +35,7 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -158,6 +159,7 @@
     @TargetApi(Build.VERSION_CODES.N)
     @Features.
     EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID, ChromeFeatureList.TAB_REPARENTING})
+    @DisabledTest(message = "https://crbug.com/1163569")
     public void testMoveTabsAcrossWindow_GTS_WithGroup() {
         // Initially, we have 5 normal tabs and 5 incognito tabs in cta1.
         final ChromeTabbedActivity cta1 = mActivityTestRule.getActivity();
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCompositorSurfaceManager.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCompositorSurfaceManager.java
index f720718a..e4911da 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCompositorSurfaceManager.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrCompositorSurfaceManager.java
@@ -76,7 +76,7 @@
 
     @Override
     public void shutDown() {
-        if (mSurfaceState == SurfaceState.PROVIDED) mClient.surfaceDestroyed(mSurface);
+        if (mSurfaceState == SurfaceState.PROVIDED) mClient.surfaceDestroyed(mSurface, true);
         mSurfaceState = SurfaceState.NOT_REQUESTED;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
index 278919e5..b5fc0b7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
@@ -160,7 +160,8 @@
         if (!ChromeStrictModeSwitch.ALLOW_STRICT_MODE_CHECKING) return;
 
         CommandLine commandLine = CommandLine.getInstance();
-        boolean shouldApplyPenalties = BuildConfig.DCHECK_IS_ON || ChromeVersionInfo.isLocalBuild()
+        boolean shouldApplyPenalties = BuildConfig.ENABLE_ASSERTS
+                || ChromeVersionInfo.isLocalBuild()
                 || commandLine.hasSwitch(ChromeSwitches.STRICT_MODE);
 
         // Enroll 1% of dev sessions into StrictMode watch. This is done client-side rather than
@@ -169,7 +170,7 @@
         // before warming the SharedPreferences (that is a violation in an of itself). We will
         // closely monitor this on dev channel.
         boolean enableStrictModeWatch =
-                (ChromeVersionInfo.isLocalBuild() && !BuildConfig.DCHECK_IS_ON)
+                (ChromeVersionInfo.isLocalBuild() && !BuildConfig.ENABLE_ASSERTS)
                 || (ChromeVersionInfo.isDevBuild() && Math.random() < UPLOAD_PROBABILITY);
         if (!shouldApplyPenalties && !enableStrictModeWatch) return;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index ec096fc..e7a4d6a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -268,7 +268,8 @@
      * When removing items, comment them out and keep existing numeric values stable.
      */
     @IntDef({IncognitoCCTCallerId.OTHER_APPS, IncognitoCCTCallerId.GOOGLE_APPS,
-            IncognitoCCTCallerId.OTHER_CHROME_FEATURES, IncognitoCCTCallerId.READER_MODE})
+            IncognitoCCTCallerId.OTHER_CHROME_FEATURES, IncognitoCCTCallerId.READER_MODE,
+            IncognitoCCTCallerId.READ_LATER})
     @Retention(RetentionPolicy.SOURCE)
     public @interface IncognitoCCTCallerId {
         int OTHER_APPS = 0;
@@ -280,9 +281,10 @@
 
         // Chrome Features
         int READER_MODE = 3;
+        int READ_LATER = 4;
 
         // Update {@link IncognitoCCTCallerId} in enums.xml when adding new items.
-        int NUM_ENTRIES = 4;
+        int NUM_ENTRIES = 5;
     }
 
     private static ComponentName getFakeComponentName(String packageName) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index 9b01feaf..b3c8f2a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -28,11 +28,13 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.IntentHandler.IncognitoCCTCallerId;
 import org.chromium.chrome.browser.LaunchIntentDispatcher;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
 import org.chromium.chrome.browser.bookmarks.bottomsheet.BookmarkBottomSheetCoordinator;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabsUiType;
 import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
+import org.chromium.chrome.browser.customtabs.IncognitoCustomTabIntentDataProvider;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
@@ -401,7 +403,7 @@
         if (bookmarkItem.getId().getType() == BookmarkType.READING_LIST
                 && !bookmarkItem.isFolder()) {
             model.setReadStatusForReadingList(bookmarkItem.getUrl(), true);
-            openUrlInCustomTab(context, bookmarkItem.getUrl().getSpec(), isIncognito);
+            openReadingListInCustomTab(context, bookmarkItem.getUrl().getSpec(), isIncognito);
         } else {
             openUrl(context, bookmarkItem.getUrl().getSpec(), openBookmarkComponentName);
         }
@@ -463,7 +465,8 @@
         IntentHandler.startActivityForTrustedIntent(intent);
     }
 
-    private static void openUrlInCustomTab(Context context, String url, boolean isOffTheRecord) {
+    private static void openReadingListInCustomTab(
+            Context context, String url, boolean isOffTheRecord) {
         CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setShowTitle(true);
         builder.setShareState(CustomTabsIntent.SHARE_STATE_ON);
@@ -475,7 +478,13 @@
         intent.setPackage(context.getPackageName());
         intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
         intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, CustomTabsUiType.READ_LATER);
-        intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, isOffTheRecord);
+
+        // Extras for incognito CCT.
+        if (isOffTheRecord) {
+            IncognitoCustomTabIntentDataProvider.addIncongitoExtrasForChromeFeatures(
+                    intent, IncognitoCCTCallerId.READ_LATER);
+        }
+
         IntentHandler.addTrustedIntentExtras(intent);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         IntentHandler.startActivityForTrustedIntent(intent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java
index 3ad283ab..a6ef442 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManager.java
@@ -23,7 +23,7 @@
         public void surfaceRedrawNeededAsync(Runnable drawingFinished);
         public void surfaceChanged(Surface surface, int format, int width, int height);
         public void surfaceCreated(Surface surface);
-        public void surfaceDestroyed(Surface surface);
+        public void surfaceDestroyed(Surface surface, boolean androidSurfaceDestroyed);
         public void unownedSurfaceDestroyed();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
index bbecd10..c7e3f07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImpl.java
@@ -190,7 +190,7 @@
         // which is fine.  We'll send destroy / create for it.  Also note that we don't actually
         // start tear-down of the owned surface; the client notifies us via doneWithUnownedSurface
         // when it is safe to do that.
-        disownClientSurface(mOwnedByClient);
+        disownClientSurface(mOwnedByClient, false);
 
         // The client now owns |mRequestedByClient|.  Notify it that it's ready.
         mOwnedByClient = mRequestedByClient;
@@ -238,7 +238,7 @@
             public void run() {
                 if (mOwnedByClient == null) return;
                 SurfaceState owned = mOwnedByClient;
-                mClient.surfaceDestroyed(owned.surfaceHolder().getSurface());
+                mClient.surfaceDestroyed(owned.surfaceHolder().getSurface(), true);
                 mOwnedByClient = null;
                 detachSurfaceNow(owned);
             }
@@ -297,7 +297,7 @@
         // since we would have removed ownership when we got surfaceDestroyed.  It's okay if the
         // client doesn't own either surface.
         assert mOwnedByClient != state;
-        disownClientSurface(mOwnedByClient);
+        disownClientSurface(mOwnedByClient, false);
 
         // The client now owns this surface, so notify it.
         mOwnedByClient = mRequestedByClient;
@@ -328,7 +328,7 @@
         // This can happen if Android destroys the surface on its own.  It's also possible that
         // we've detached it, if a destroy was pending.  Either way, notify the client.
         if (state == mOwnedByClient) {
-            disownClientSurface(mOwnedByClient);
+            disownClientSurface(mOwnedByClient, true);
 
             // Do not re-request the surface here.  If android gives the surface back, then we'll
             // re-signal the client about construction.
@@ -426,10 +426,10 @@
      * the surface has been destroyed (recall that ownership involves getting created).  It's okay
      * if |state| is null or isn't owned by the client.
      */
-    private void disownClientSurface(SurfaceState state) {
+    private void disownClientSurface(SurfaceState state, boolean surfaceDestroyed) {
         if (mOwnedByClient != state || state == null) return;
 
-        mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder().getSurface());
+        mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder().getSurface(), surfaceDestroyed);
         mOwnedByClient = null;
     }
 
@@ -460,7 +460,7 @@
 
         // The surface isn't attached, or was attached but wasn't currently valid.  Either way,
         // we're not going to get a destroy, so notify the client now if needed.
-        disownClientSurface(state);
+        disownClientSurface(state, false);
 
         // If the client has since re-requested the surface, then start construction.
         if (state == mRequestedByClient) attachSurfaceNow(mRequestedByClient);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
index 7d41729..4f861fb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorView.java
@@ -410,9 +410,17 @@
     }
 
     @Override
-    public void surfaceDestroyed(Surface surface) {
+    public void surfaceDestroyed(Surface surface, boolean androidSurfaceDestroyed) {
         if (mNativeCompositorView == 0) return;
 
+        // When we switch from Chrome to other app we can't detach child surface controls because it
+        // leads to a visible hole: b/157439199. To avoid this we don't detach surfaces if the
+        // surface is going to be destroyed, they will be detached and freed by OS.
+        if (androidSurfaceDestroyed) {
+            CompositorViewJni.get().preserveChildSurfaceControls(
+                    mNativeCompositorView, CompositorView.this);
+        }
+
         CompositorViewJni.get().surfaceDestroyed(mNativeCompositorView, CompositorView.this);
     }
 
@@ -673,5 +681,6 @@
         void cacheBackBufferForCurrentSurface(long nativeCompositorView, CompositorView caller);
         void evictCachedBackBuffer(long nativeCompositorView, CompositorView caller);
         void onTabChanged(long nativeCompositorView, CompositorView caller);
+        void preserveChildSurfaceControls(long nativeCompositorView, CompositorView caller);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserver.java
index 13fabb5..d4545c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserver.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.feature_engagement;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.UserData;
 import org.chromium.base.metrics.RecordHistogram;
@@ -28,6 +29,7 @@
     public static final int SCREENSHOT_ACTION_COUNT = 3;
 
     private static final Class<ScreenshotTabObserver> USER_DATA_KEY = ScreenshotTabObserver.class;
+    private Runnable mOnReportCompleteForTesting;
 
     /**
      * Gets the existing observer if it exists, otherwise creates one.
@@ -122,5 +124,14 @@
 
         mScreenshotsTaken = 0;
         mScreenshotAction = SCREENSHOT_ACTION_NONE;
+        if (mOnReportCompleteForTesting != null) {
+            mOnReportCompleteForTesting.run();
+            mOnReportCompleteForTesting = null;
+        }
+    }
+
+    @VisibleForTesting
+    public void setOnReportCompleteForTesting(Runnable onReportCompleteForTesting) {
+        mOnReportCompleteForTesting = onReportCompleteForTesting;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index 2ace6da..8bc56b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -36,6 +36,7 @@
 import org.chromium.components.webapps.WebappsIconUtils;
 import org.chromium.webapk.lib.client.WebApkVersion;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -129,10 +130,12 @@
 
         boolean gotManifest = (fetchedInfo != null);
         @WebApkUpdateReason
-        int updateReason = needsUpdate(mInfo, fetchedInfo, primaryIconUrl, splashIconUrl);
-        boolean needsUpgrade = (updateReason != WebApkUpdateReason.NONE);
+        List<Integer> updateReasons =
+                generateUpdateReasons(mInfo, fetchedInfo, primaryIconUrl, splashIconUrl);
+        boolean needsUpgrade = !updateReasons.isEmpty();
         if (mStorage.shouldForceUpdate() && needsUpgrade) {
-            updateReason = WebApkUpdateReason.MANUALLY_TRIGGERED;
+            // Add to the front of the list to designate it as the primary reason.
+            updateReasons.add(0, WebApkUpdateReason.MANUALLY_TRIGGERED);
         }
         Log.i(TAG, "Got Manifest: " + gotManifest);
         Log.i(TAG, "WebAPK upgrade needed: " + needsUpgrade);
@@ -166,7 +169,7 @@
 
         if (fetchedInfo != null) {
             buildUpdateRequestAndSchedule(fetchedInfo, primaryIconUrl, splashIconUrl,
-                    false /* isManifestStale */, updateReason);
+                    false /* isManifestStale */, updateReasons);
             return;
         }
 
@@ -174,7 +177,7 @@
         // our Web Manifest data if the server's Web Manifest data is newer. This scenario can
         // occur if the Web Manifest is temporarily unreachable.
         buildUpdateRequestAndSchedule(mInfo, "" /* primaryIconUrl */, "" /* splashIconUrl */,
-                true /* isManifestStale */, updateReason);
+                true /* isManifestStale */, updateReasons);
     }
 
     /**
@@ -186,7 +189,7 @@
 
     /** Builds proto to send to the WebAPK server. */
     private void buildUpdateRequestAndSchedule(WebappInfo info, String primaryIconUrl,
-            String splashIconUrl, boolean isManifestStale, @WebApkUpdateReason int updateReason) {
+            String splashIconUrl, boolean isManifestStale, List<Integer> updateReasons) {
         Callback<Boolean> callback = (success) -> {
             if (!success) {
                 onFinishedUpdate(mStorage, WebApkInstallResult.FAILURE, false /* relaxUpdates*/);
@@ -196,7 +199,7 @@
         };
         String updateRequestPath = mStorage.createAndSetUpdateRequestFilePath(info);
         storeWebApkUpdateRequestToFile(updateRequestPath, info, primaryIconUrl, splashIconUrl,
-                isManifestStale, updateReason, callback);
+                isManifestStale, updateReasons, callback);
     }
 
     /** Schedules update for when WebAPK is not running. */
@@ -335,20 +338,25 @@
     }
 
     /**
-     * Checks whether the WebAPK needs to be updated.
+     * Returns a list of reasons why the WebAPK needs to be updated.
      * @param oldInfo        Data extracted from WebAPK manifest.
      * @param fetchedInfo    Fetched data for Web Manifest.
      * @param primaryIconUrl The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
      *                       suited for use as the launcher icon on this device.
      * @param splashIconUrl  The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
      *                       suited for use as the splash icon on this device.
-     * @return reason that an update is needed or {@link WebApkUpdateReason#NONE} if an update is
-     *         not needed.
+     * @return reasons that an update is needed or an empty list if an update is not needed.
      */
-    private static @WebApkUpdateReason int needsUpdate(WebappInfo oldInfo, WebappInfo fetchedInfo,
+    private static List<Integer> generateUpdateReasons(WebappInfo oldInfo, WebappInfo fetchedInfo,
             String primaryIconUrl, String splashIconUrl) {
-        if (isShellApkVersionOutOfDate(oldInfo)) return WebApkUpdateReason.OLD_SHELL_APK;
-        if (fetchedInfo == null) return WebApkUpdateReason.NONE;
+        List<Integer> updateReasons = new ArrayList<>();
+
+        if (isShellApkVersionOutOfDate(oldInfo)) {
+            updateReasons.add(WebApkUpdateReason.OLD_SHELL_APK);
+        }
+        if (fetchedInfo == null) {
+            return updateReasons;
+        }
 
         // We should have computed the Murmur2 hashes for the bitmaps at the primary icon URL and
         // the splash icon for {@link fetchedInfo} (but not the other icon URLs.)
@@ -362,37 +370,48 @@
                 oldInfo.iconUrlToMurmur2HashMap(), splashIconUrl);
 
         if (!TextUtils.equals(primaryIconMurmur2Hash, fetchedPrimaryIconMurmur2Hash)) {
-            return WebApkUpdateReason.PRIMARY_ICON_HASH_DIFFERS;
-        } else if (!TextUtils.equals(splashIconMurmur2Hash, fetchedSplashIconMurmur2Hash)) {
-            return WebApkUpdateReason.SPLASH_ICON_HASH_DIFFERS;
-        } else if (!UrlUtilities.urlsMatchIgnoringFragments(
-                           oldInfo.scopeUrl(), fetchedInfo.scopeUrl())) {
-            return WebApkUpdateReason.SCOPE_DIFFERS;
-        } else if (!UrlUtilities.urlsMatchIgnoringFragments(
-                           oldInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())) {
-            return WebApkUpdateReason.START_URL_DIFFERS;
-        } else if (!TextUtils.equals(oldInfo.shortName(), fetchedInfo.shortName())) {
-            return WebApkUpdateReason.SHORT_NAME_DIFFERS;
-        } else if (!TextUtils.equals(oldInfo.name(), fetchedInfo.name())) {
-            return WebApkUpdateReason.NAME_DIFFERS;
-        } else if (oldInfo.backgroundColor() != fetchedInfo.backgroundColor()) {
-            return WebApkUpdateReason.BACKGROUND_COLOR_DIFFERS;
-        } else if (oldInfo.toolbarColor() != fetchedInfo.toolbarColor()) {
-            return WebApkUpdateReason.THEME_COLOR_DIFFERS;
-        } else if (oldInfo.orientation() != fetchedInfo.orientation()) {
-            return WebApkUpdateReason.ORIENTATION_DIFFERS;
-        } else if (oldInfo.displayMode() != fetchedInfo.displayMode()) {
-            return WebApkUpdateReason.DISPLAY_MODE_DIFFERS;
-        } else if (!WebApkShareTarget.equals(oldInfo.shareTarget(), fetchedInfo.shareTarget())) {
-            return WebApkUpdateReason.WEB_SHARE_TARGET_DIFFERS;
-        } else if (oldInfo.isIconAdaptive() != fetchedInfo.isIconAdaptive()
+            updateReasons.add(WebApkUpdateReason.PRIMARY_ICON_HASH_DIFFERS);
+        }
+        if (!TextUtils.equals(splashIconMurmur2Hash, fetchedSplashIconMurmur2Hash)) {
+            updateReasons.add(WebApkUpdateReason.SPLASH_ICON_HASH_DIFFERS);
+        }
+        if (!UrlUtilities.urlsMatchIgnoringFragments(oldInfo.scopeUrl(), fetchedInfo.scopeUrl())) {
+            updateReasons.add(WebApkUpdateReason.SCOPE_DIFFERS);
+        }
+        if (!UrlUtilities.urlsMatchIgnoringFragments(
+                    oldInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())) {
+            updateReasons.add(WebApkUpdateReason.START_URL_DIFFERS);
+        }
+        if (!TextUtils.equals(oldInfo.shortName(), fetchedInfo.shortName())) {
+            updateReasons.add(WebApkUpdateReason.SHORT_NAME_DIFFERS);
+        }
+        if (!TextUtils.equals(oldInfo.name(), fetchedInfo.name())) {
+            updateReasons.add(WebApkUpdateReason.NAME_DIFFERS);
+        }
+        if (oldInfo.backgroundColor() != fetchedInfo.backgroundColor()) {
+            updateReasons.add(WebApkUpdateReason.BACKGROUND_COLOR_DIFFERS);
+        }
+        if (oldInfo.toolbarColor() != fetchedInfo.toolbarColor()) {
+            updateReasons.add(WebApkUpdateReason.THEME_COLOR_DIFFERS);
+        }
+        if (oldInfo.orientation() != fetchedInfo.orientation()) {
+            updateReasons.add(WebApkUpdateReason.ORIENTATION_DIFFERS);
+        }
+        if (oldInfo.displayMode() != fetchedInfo.displayMode()) {
+            updateReasons.add(WebApkUpdateReason.DISPLAY_MODE_DIFFERS);
+        }
+        if (!WebApkShareTarget.equals(oldInfo.shareTarget(), fetchedInfo.shareTarget())) {
+            updateReasons.add(WebApkUpdateReason.WEB_SHARE_TARGET_DIFFERS);
+        }
+        if (oldInfo.isIconAdaptive() != fetchedInfo.isIconAdaptive()
                 && (!fetchedInfo.isIconAdaptive()
                         || WebappsIconUtils.doesAndroidSupportMaskableIcons())) {
-            return WebApkUpdateReason.PRIMARY_ICON_MASKABLE_DIFFERS;
-        } else if (shortcutsDiffer(oldInfo.shortcutItems(), fetchedInfo.shortcutItems())) {
-            return WebApkUpdateReason.SHORTCUTS_DIFFER;
+            updateReasons.add(WebApkUpdateReason.PRIMARY_ICON_MASKABLE_DIFFERS);
         }
-        return WebApkUpdateReason.NONE;
+        if (shortcutsDiffer(oldInfo.shortcutItems(), fetchedInfo.shortcutItems())) {
+            updateReasons.add(WebApkUpdateReason.SHORTCUTS_DIFFER);
+        }
+        return updateReasons;
     }
 
     /**
@@ -411,7 +430,7 @@
 
     protected void storeWebApkUpdateRequestToFile(String updateRequestPath, WebappInfo info,
             String primaryIconUrl, String splashIconUrl, boolean isManifestStale,
-            @WebApkUpdateReason int updateReason, Callback<Boolean> callback) {
+            List<Integer> updateReasons, Callback<Boolean> callback) {
         int versionCode = info.webApkVersionCode();
         int size = info.iconUrlToMurmur2HashMap().size();
         String[] iconUrls = new String[size];
@@ -449,6 +468,11 @@
             shareTargetParamAccepts = shareTarget.getFileAccepts();
         }
 
+        int[] updateReasonsArray = new int[updateReasons.size()];
+        for (int j = 0; j < updateReasons.size(); j++) {
+            updateReasonsArray[j] = updateReasons.get(j);
+        }
+
         WebApkUpdateManagerJni.get().storeWebApkUpdateRequestToFile(updateRequestPath,
                 info.manifestStartUrl(), info.scopeUrl(), info.name(), info.shortName(),
                 primaryIconUrl, info.icon().bitmap(), info.isIconAdaptive(), splashIconUrl,
@@ -457,7 +481,7 @@
                 shareTargetParamTitle, shareTargetParamText, shareTargetIsMethodPost,
                 shareTargetIsEncTypeMultipart, shareTargetParamFileNames, shareTargetParamAccepts,
                 shortcuts, info.manifestUrl(), info.webApkPackageName(), versionCode,
-                isManifestStale, updateReason, callback);
+                isManifestStale, updateReasonsArray, callback);
     }
 
     @NativeMethods
@@ -472,7 +496,7 @@
                 boolean shareTargetParamIsEncTypeMultipart, String[] shareTargetParamFileNames,
                 Object[] shareTargetParamAccepts, String[][] shortcuts, String manifestUrl,
                 String webApkPackage, int webApkVersion, boolean isManifestStale,
-                @WebApkUpdateReason int updateReason, Callback<Boolean> callback);
+                int[] updateReasons, Callback<Boolean> callback);
         public void updateWebApkFromFile(String updateRequestPath, WebApkUpdateCallback callback);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index 7e2b1bf5..45c7657 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -1197,9 +1197,7 @@
                 CustomTabActivity.class, Stage.CREATED, () -> { readingListRow.performClick(); });
         CriteriaHelper.pollUiThread(() -> activity.getActivityTab() != null);
         Intent customTabIntent = activity.getInitialIntent();
-        Assert.assertTrue(customTabIntent.hasExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB));
-        Assert.assertFalse(
-                customTabIntent.getBooleanExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false));
+        Assert.assertFalse(customTabIntent.hasExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB));
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { Assert.assertTrue(activity.getActivityTab().getUrl().equals(mTestUrlA)); });
         activity.finish();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
index accba6c..8f283a1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotTabObserverTest.java
@@ -12,6 +12,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.MetricsUtils;
@@ -23,6 +24,7 @@
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tests for ScreenshotTabObserver class.
@@ -57,19 +59,21 @@
         });
     }
 
-    // Disabled due to flakiness. https://crbug.com/901856
     @Test
     @SmallTest
-    @DisabledTest
-    public void testScreenshotNumberReportingOne() {
+    public void testScreenshotNumberReportingOne() throws TimeoutException {
         MetricsUtils.HistogramDelta histogramDeltaZeroScreenshots =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 0);
         MetricsUtils.HistogramDelta histogramDeltaOneScreenshot =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 1);
         MetricsUtils.HistogramDelta histogramDeltaTwoScreenshots =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 2);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        setupOnReportCompleteCallbackHelper(callbackHelper);
+        int count = callbackHelper.getCallCount();
         mObserver.onScreenshotTaken();
         TestThreadUtils.runOnUiThreadBlocking(mTab::destroy);
+        callbackHelper.waitForCallback(count);
         // Check the first 3 buckets of the NumberOfScrenshots metric.
         Assert.assertEquals("Should be no pages with zero snapshots reported", 0,
                 histogramDeltaZeroScreenshots.getDelta());
@@ -79,34 +83,38 @@
                 histogramDeltaTwoScreenshots.getDelta());
     }
 
-    // Disabled due to flakiness. https://crbug.com/901856
     @Test
     @SmallTest
-    @DisabledTest
-    public void testScreenshotNumberReportingTwo() {
+    public void testScreenshotNumberReportingTwo() throws TimeoutException {
         MetricsUtils.HistogramDelta histogramDeltaTwoScreenshots =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.ScreenshotsPerPage", 2);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        setupOnReportCompleteCallbackHelper(callbackHelper);
+        int count = callbackHelper.getCallCount();
         mObserver.onScreenshotTaken();
         mObserver.onScreenshotTaken();
         TestThreadUtils.runOnUiThreadBlocking(mTab::destroy);
+        callbackHelper.waitForCallback(count);
         Assert.assertEquals("Should be one page with two snapshots reported", 1,
                 histogramDeltaTwoScreenshots.getDelta());
     }
 
-    // Disabled due to flakiness. https://crbug.com/901856
     @Test
     @SmallTest
-    @DisabledTest
-    public void testScreenshotActionReporting() {
+    public void testScreenshotActionReporting() throws TimeoutException {
         MetricsUtils.HistogramDelta histogramDeltaScreenshotNoAction =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.Action", 0);
         MetricsUtils.HistogramDelta histogramDeltaScreenshotShareAction =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.Action", 1);
         MetricsUtils.HistogramDelta histogramDeltaScreenshotDownloadIPHAction =
                 new MetricsUtils.HistogramDelta("Tab.Screenshot.Action", 2);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        setupOnReportCompleteCallbackHelper(callbackHelper);
+        int count = callbackHelper.getCallCount();
         mObserver.onScreenshotTaken();
         mObserver.onActionPerformedAfterScreenshot(ScreenshotTabObserver.SCREENSHOT_ACTION_SHARE);
         TestThreadUtils.runOnUiThreadBlocking(mTab::destroy);
+        callbackHelper.waitForCallback(count);
         Assert.assertEquals("Should be no none actions reported", 0,
                 histogramDeltaScreenshotNoAction.getDelta());
         Assert.assertEquals("Should be one share action reported", 1,
@@ -114,4 +122,13 @@
         Assert.assertEquals("Should be no download IPH actions reported", 0,
                 histogramDeltaScreenshotDownloadIPHAction.getDelta());
     }
+
+    private void setupOnReportCompleteCallbackHelper(CallbackHelper callbackHelper) {
+        mObserver.setOnReportCompleteForTesting(new Runnable() {
+            @Override
+            public void run() {
+                callbackHelper.notifyCalled();
+            }
+        });
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
index 712106d..d5b7250 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
@@ -167,6 +167,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "https://crbug.com/1188377")
     public void testShowAfterHide() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/SadTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/SadTabTest.java
index bbcb00b..cbd17b5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/SadTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/SadTabTest.java
@@ -146,6 +146,7 @@
         reloadSadTab(tab);
         Assert.assertTrue(isShowingSadTab(tab));
         actualText = getSadTabButton(tab).getText().toString();
+        Assert.assertTrue(showSendFeedbackView(tab));
         Assert.assertEquals(
                 "Expected the sad tab button to have the feedback label after the tab button "
                         + "crashes twice in a row.",
@@ -185,6 +186,15 @@
         });
     }
 
+    private static boolean showSendFeedbackView(final Tab tab) {
+        try {
+            return TestThreadUtils.runOnUiThreadBlocking(
+                    () -> SadTab.from(tab).showSendFeedbackView());
+        } catch (ExecutionException e) {
+            return false; // Make tests fail when an exception is thrown.
+        }
+    }
+
     /**
      * If there is a SadTabView, this method will get the button for the sad tab.
      * @param tab The tab that needs to contain a SadTabView.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
index 1f4b524b..3d137caf1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
@@ -39,6 +39,7 @@
 import org.chromium.webapk.lib.client.WebApkVersion;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -84,18 +85,20 @@
     private Tab mTab;
     private EmbeddedTestServer mTestServer;
 
+    private List<Integer> mLastUpdateReasons;
+
     /**
      * Subclass of {@link WebApkUpdateManager} which notifies the {@link CallbackHelper} passed to
      * the constructor when it has been determined whether an update is needed.
      */
-    private static class TestWebApkUpdateManager extends WebApkUpdateManager {
+    private class TestWebApkUpdateManager extends WebApkUpdateManager {
         private CallbackHelper mWaiter;
-        private boolean mNeedsUpdate;
 
         public TestWebApkUpdateManager(CallbackHelper waiter, ActivityTabProvider tabProvider,
                 ActivityLifecycleDispatcher lifecycleDispatcher) {
             super(tabProvider, lifecycleDispatcher);
             mWaiter = waiter;
+            mLastUpdateReasons = new ArrayList<>();
         }
 
         @Override
@@ -108,12 +111,8 @@
         @Override
         protected void storeWebApkUpdateRequestToFile(String updateRequestPath, WebappInfo info,
                 String primaryIconUrl, String splashIconUrl, boolean isManifestStale,
-                @WebApkUpdateReason int updateReason, Callback<Boolean> callback) {
-            mNeedsUpdate = true;
-        }
-
-        public boolean needsUpdate() {
-            return mNeedsUpdate;
+                List<Integer> updateReasons, Callback<Boolean> callback) {
+            mLastUpdateReasons = updateReasons;
         }
     }
 
@@ -190,7 +189,11 @@
         });
         waiter.waitForCallback(0);
 
-        return updateManager.needsUpdate();
+        return !mLastUpdateReasons.isEmpty();
+    }
+
+    private void assertUpdateReasonsEqual(@WebApkUpdateReason Integer... reasons) {
+        Assert.assertEquals(Arrays.asList(reasons), mLastUpdateReasons);
     }
 
     /**
@@ -228,6 +231,7 @@
         WebappTestPage.navigateToServiceWorkerPageWithManifest(
                 mTestServer, mTab, WEBAPK_MANIFEST_URL);
         Assert.assertTrue(checkUpdateNeeded(creationData));
+        assertUpdateReasonsEqual(WebApkUpdateReason.START_URL_DIFFERS);
     }
 
     @Test
@@ -276,6 +280,9 @@
 
         Assert.assertEquals(WebappsIconUtils.doesAndroidSupportMaskableIcons(),
                 checkUpdateNeeded(creationData));
+        if (WebappsIconUtils.doesAndroidSupportMaskableIcons()) {
+            assertUpdateReasonsEqual(WebApkUpdateReason.PRIMARY_ICON_MASKABLE_DIFFERS);
+        }
     }
 
     @Test
@@ -298,4 +305,26 @@
                 mTestServer, mTab, WEBAPK_MANIFEST_TOO_MANY_SHORTCUTS_URL);
         Assert.assertFalse(checkUpdateNeeded(creationData));
     }
+
+    @Test
+    @MediumTest
+    @Feature({"WebApk"})
+    public void testMultipleUpdateReasons() throws Exception {
+        CreationData creationData = defaultCreationData();
+        creationData.startUrl =
+                mTestServer.getURL("/chrome/test/data/banners/manifest_test_page.html");
+
+        creationData.name += "!";
+        creationData.shortName += "!";
+        creationData.backgroundColor -= 1;
+        creationData.iconUrlToMurmur2HashMap.put(
+                mTestServer.getURL(WEBAPK_ICON_URL), WEBAPK_ICON_MURMUR2_HASH + "1");
+
+        WebappTestPage.navigateToServiceWorkerPageWithManifest(
+                mTestServer, mTab, WEBAPK_MANIFEST_URL);
+        Assert.assertTrue(checkUpdateNeeded(creationData));
+        assertUpdateReasonsEqual(WebApkUpdateReason.PRIMARY_ICON_HASH_DIFFERS,
+                WebApkUpdateReason.SPLASH_ICON_HASH_DIFFERS, WebApkUpdateReason.SHORT_NAME_DIFFERS,
+                WebApkUpdateReason.NAME_DIFFERS, WebApkUpdateReason.BACKGROUND_COLOR_DIFFERS);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java
index 33938aa1..b6af10d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerImplTest.java
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -9,6 +9,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
@@ -218,7 +220,7 @@
         verify(mCallback, times(0)).surfaceCreated(ArgumentMatchers.<Surface>any());
         verify(mCallback, times(0))
                 .surfaceChanged(ArgumentMatchers.<Surface>any(), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Check that there's an opaque SurfaceView .
         assertEquals(1, mLayout.getChildCount());
@@ -227,7 +229,7 @@
         // Verify that we are notified when the surface is created.
         callbackFor(opaque).surfaceCreated(opaque.getHolder());
         verify(mCallback, times(1)).surfaceCreated(eq(opaque.getHolder().getSurface()));
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Verify that we are notified when the surface is changed.
         sendSurfaceChanged(opaque, PixelFormat.OPAQUE, 320, 240);
@@ -235,14 +237,14 @@
         verify(mCallback, times(1))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), eq(mActualFormat), eq(mWidth),
                         eq(mHeight));
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Verify that we are notified when the surface is destroyed.
         callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
         verify(mCallback, times(1)).surfaceCreated(eq(opaque.getHolder().getSurface()));
         verify(mCallback, times(1))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), true);
     }
 
     @Test
@@ -254,7 +256,7 @@
         SurfaceView translucent = requestThenCreateSurface(PixelFormat.TRANSLUCENT);
 
         // Verify that we received a destroy for |opaque| and created / changed for |translucent|.
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), false);
         verify(mCallback, times(1)).surfaceCreated(translucent.getHolder().getSurface());
         verify(mCallback, times(1))
                 .surfaceChanged(
@@ -280,7 +282,7 @@
         verify(mCallback, times(1)).surfaceCreated(eq(opaque.getHolder().getSurface()));
         verify(mCallback, times(1))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Surface is currently valid.  Request again.  We should get back a destroy and create.
         assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
@@ -288,7 +290,7 @@
         verify(mCallback, times(2))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), eq(mActualFormat), eq(mWidth),
                         eq(mHeight));
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), false);
         assertEquals(1, mLayout.getChildCount());
     }
 
@@ -301,14 +303,14 @@
         verify(mCallback, times(0)).surfaceCreated(opaque.getHolder().getSurface());
         verify(mCallback, times(0))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Request again.  We shouldn't get any callbacks, since the surface is still pending.
         assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
         verify(mCallback, times(0)).surfaceCreated(opaque.getHolder().getSurface());
         verify(mCallback, times(0))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Only the opaque view should be attached.
         assertEquals(1, mLayout.getChildCount());
@@ -321,7 +323,7 @@
         verify(mCallback, times(1))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), eq(mActualFormat), eq(mWidth),
                         eq(mHeight));
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
     }
 
     @Test
@@ -333,7 +335,7 @@
         verify(mCallback, times(0)).surfaceCreated(opaque.getHolder().getSurface());
         verify(mCallback, times(0))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // Request translucent.  We should get no callbacks, but both views should be attached.
         SurfaceView translucent = requestSurface(PixelFormat.TRANSLUCENT);
@@ -348,7 +350,7 @@
         assertEquals(1, mLayout.getChildCount());
         verify(mCallback, times(0)).surfaceCreated(opaque.getHolder().getSurface());
         verify(mCallback, times(0)).surfaceCreated(translucent.getHolder().getSurface());
-        verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(0)).surfaceDestroyed(any(), anyBoolean());
 
         // When we create the translucent surface, we should be notified.
         callbackFor(translucent).surfaceCreated(translucent.getHolder());
@@ -375,7 +377,7 @@
         // synthetic 'created'.
         assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
         verify(mCallback, times(2)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), false);
         verify(mCallback, times(0))
                 .surfaceChanged(eq(opaque.getHolder().getSurface()), anyInt(), anyInt(), anyInt());
 
@@ -400,20 +402,20 @@
         // surface should be detached.
         mManager.recreateSurface();
         verify(mCallback, times(1)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), true);
         assertEquals(0, mLayout.getChildCount());
 
         // When the surface really is destroyed, it should be re-attached.  We should not be
         // notified again, though.
         callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
         verify(mCallback, times(1)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), true);
         assertEquals(1, mLayout.getChildCount());
 
         // When the surface is re-created, we should be notified.
         callbackFor(opaque).surfaceCreated(opaque.getHolder());
         verify(mCallback, times(2)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), true);
         assertEquals(1, mLayout.getChildCount());
     }
 
@@ -443,15 +445,15 @@
         callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
         assertEquals(2, mLayout.getChildCount());
         verify(mCallback, times(1)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
-        verify(mCallback, times(0)).surfaceDestroyed(translucent.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), false);
+        verify(mCallback, times(0)).surfaceDestroyed(translucent.getHolder().getSurface(), true);
 
         // When the opaque surface becomes available, we'll get the synthetic destroy for the
         // translucent one that we lost ownership of, and the real create for the opaque one.
         callbackFor(opaque).surfaceCreated(opaque.getHolder());
         assertEquals(2, mLayout.getChildCount());
         verify(mCallback, times(2)).surfaceCreated(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface());
-        verify(mCallback, times(1)).surfaceDestroyed(translucent.getHolder().getSurface());
+        verify(mCallback, times(1)).surfaceDestroyed(translucent.getHolder().getSurface(), false);
+        verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder().getSurface(), false);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
index 61460953..b7869a91 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
@@ -370,7 +370,7 @@
 
     @Test
     public void testLoadUrl_NativeNotInitialized() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             // clang-format off
             try {
                 mMediator.loadUrl(TEST_URL, PageTransition.TYPED, 0);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
index c9d9f67..e39418f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
@@ -141,7 +141,7 @@
                 boolean shareTargetParamIsEncTypeMultipart, String[] shareTargetParamFileNames,
                 Object[] shareTargetParamAccepts, String[][] shortcuts, String manifestUrl,
                 String webApkPackage, int webApkVersion, boolean isManifestStale,
-                @WebApkUpdateReason int updateReason, Callback<Boolean> callback) {}
+                int[] updateReasons, Callback<Boolean> callback) {}
 
         @Override
         public void updateWebApkFromFile(
@@ -210,7 +210,7 @@
         @Override
         protected void storeWebApkUpdateRequestToFile(String updateRequestPath, WebappInfo info,
                 String primaryIconUrl, String splashIconUrl, boolean isManifestStale,
-                @WebApkUpdateReason int updateReason, Callback<Boolean> callback) {
+                List<Integer> updateReasons, Callback<Boolean> callback) {
             mStoreUpdateRequestCallback = callback;
             mUpdateName = info.name();
             writeRandomTextToFile(updateRequestPath);
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 26b1456..ea39061 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -5132,7 +5132,7 @@
   </message>
 
   <!-- Strings for Plugin VM -->
-  <message name="IDS_PLUGIN_VM_APP_NAME" desc="Name of the 'Parallels' app.">
+  <message name="IDS_PLUGIN_VM_APP_NAME" desc="Product name for 'Parallels Desktop'. Should not be translated as it is a third party name." translateable="false">
     Parallels Desktop
   </message>
   <message name="IDS_PLUGIN_VM_INSTALLER_CONFIRMATION_TITLE" desc="Title of the Plugin VM installer when asking user to start installation.">
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index eb63a544..1afb22d 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5807,7 +5807,13 @@
         Press to go back, context menu to see history
       </message>
       <message name="IDS_TOOLTIP_CHROMELABS_BUTTON" desc="The tooltip for the Chrome Labs button in the toolbar">
-      Experiments
+        Experiments
+      </message>
+      <message name="IDS_TOOLTIP_CHROMELABS_COMBOBOX" desc="The tooltip for the Chrome Labs combobox to select between experiment states">
+        Select experiment state
+      </message>
+      <message name="IDS_TOOLTIP_CHROMELABS_FEEDBACK_BUTTON" desc="The tooltip for the Chrome Labs Send Feedback button">
+        Send feedback for <ph name="EXPERIMENT_NAME">$1<ex>Tab Scrolling</ex></ph>
       </message>
       <message name="IDS_TOOLTIP_FORWARD" desc="The tooltip for forward button">
         Click to go forward, hold to see history
@@ -11197,6 +11203,52 @@
     <message name="IDS_CHROMELABS_SEND_FEEDBACK_DESCRIPTION_PLACEHOLDER" desc="The placeholder text for the send feedback dialog to submit feedback for each experiment.">
       Send feedback for <ph name="EXPERIMENT_NAME">$1<ex>Tab Scrolling</ex></ph>.
     </message>
+    <message name="IDS_CHROMELABS_DEFAULT" desc="Label for combobox option for the default experiment state">
+      Default
+    </message>
+    <message name="IDS_CHROMELABS_ENABLED" desc="Label for combobox option for the enable experiment state">
+      Enabled
+    </message>
+    <message name="IDS_CHROMELABS_DISABLED" desc="Label for combobox option for the disabled experiment state">
+      Disabled
+    </message>
+    <message name = "IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME" desc="Label for combobox option for an enabled variation that will have a description describing what variation is enabled in the placeholder.">
+      Enabled – <ph name="VARIATION_NAME">$1<ex>tabs shrink to pinned tab width</ex></ph>
+    </message>
+
+    <!-- ChromeLabs Read Later-->
+    <message name="IDS_READ_LATER_EXPERIMENT_NAME" desc="Name for Read Later experiment">
+      Reading List
+    </message>
+    <message name="IDS_READ_LATER_EXPERIMENT_DESCRIPTION" desc="Description for Read Later experiment">
+      Right click on a tab or click the Bookmark icon to add tabs to a reading list. Access from the Bookmarks bar.
+    </message>
+    <!-- ChromeLabs Tab Scrolling-->
+    <message name="IDS_TAB_SCROLLING_EXPERIMENT_NAME" desc="Name for Tab Scrolling experiment">
+      Tab Scrolling
+    </message>
+    <message name="IDS_TAB_SCROLLING_EXPERIMENT_DESCRIPTION" desc="Description for Tab Scrolling experiment">
+      Enables tab strip to scroll left and right when full.
+    </message>
+    <message name="IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH" desc="Label describing tab behavior will shrink to pinned tab width">
+      Tabs shrink to pinned tab width
+    </message>
+    <message name="IDS_TABS_SHRINK_TO_MEDIUM_WIDTH" desc="Label describing tab behavior will shrink to medium width">
+      Tabs shrink to a medium width
+    </message>
+    <message name="IDS_TABS_SHRINK_TO_LARGE_WIDTH" desc="Label describing tab behavior will shrink to large width">
+      Tabs shrink to a large width
+    </message>
+    <message name="IDS_TABS_DO_NOT_SHRINK" desc="Label describing tab behavior will not shrink">
+      Tabs don't shrink
+    </message>
+    <!-- ChromeLabs Tab Search -->
+    <message name="IDS_TAB_SEARCH_EXPERIMENT_NAME" desc="Name for Tab Search experiment">
+      Tab Search
+    </message>
+    <message name="IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION" desc="Description for Tab Search experiment">
+      Enable a popup bubble in Top Chrome UI to search over currently open tabs.
+    </message>
   </messages>
 </release>
 </grit>
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMELABS_DEFAULT.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMELABS_DEFAULT.png.sha1
new file mode 100644
index 0000000..459b2298
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMELABS_DEFAULT.png.sha1
@@ -0,0 +1 @@
+081cdb4262257d309e0aa1615268aa62b315f41b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMELABS_DISABLED.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMELABS_DISABLED.png.sha1
new file mode 100644
index 0000000..459b2298
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMELABS_DISABLED.png.sha1
@@ -0,0 +1 @@
+081cdb4262257d309e0aa1615268aa62b315f41b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED.png.sha1
new file mode 100644
index 0000000..459b2298
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED.png.sha1
@@ -0,0 +1 @@
+081cdb4262257d309e0aa1615268aa62b315f41b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME.png.sha1
new file mode 100644
index 0000000..39527243
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME.png.sha1
@@ -0,0 +1 @@
+c6b2089796a6e244a581d75284303fab7af61463
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..b1799157
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+debed67a4f865a0340e1d348bee521ed061a93ef
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_NAME.png.sha1
new file mode 100644
index 0000000..b1799157
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_READ_LATER_EXPERIMENT_NAME.png.sha1
@@ -0,0 +1 @@
+debed67a4f865a0340e1d348bee521ed061a93ef
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TABS_DO_NOT_SHRINK.png.sha1 b/chrome/app/generated_resources_grd/IDS_TABS_DO_NOT_SHRINK.png.sha1
new file mode 100644
index 0000000..4b680fba
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TABS_DO_NOT_SHRINK.png.sha1
@@ -0,0 +1 @@
+358a467ed714b4949a8660b8dd053fcec129561a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_LARGE_WIDTH.png.sha1 b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_LARGE_WIDTH.png.sha1
new file mode 100644
index 0000000..4b680fba
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_LARGE_WIDTH.png.sha1
@@ -0,0 +1 @@
+358a467ed714b4949a8660b8dd053fcec129561a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_MEDIUM_WIDTH.png.sha1 b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_MEDIUM_WIDTH.png.sha1
new file mode 100644
index 0000000..4b680fba
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_MEDIUM_WIDTH.png.sha1
@@ -0,0 +1 @@
+358a467ed714b4949a8660b8dd053fcec129561a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH.png.sha1 b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH.png.sha1
new file mode 100644
index 0000000..4b680fba
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH.png.sha1
@@ -0,0 +1 @@
+358a467ed714b4949a8660b8dd053fcec129561a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..a9717410
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+22e9f21270b9004f7984e3dba0909d6ffeb12a6c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_NAME.png.sha1
new file mode 100644
index 0000000..a9717410
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_SCROLLING_EXPERIMENT_NAME.png.sha1
@@ -0,0 +1 @@
+22e9f21270b9004f7984e3dba0909d6ffeb12a6c
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..e68acc5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+71b4ef1b62d61ec7fe64d09c600e069450c6fefe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1
new file mode 100644
index 0000000..e68acc5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_SEARCH_EXPERIMENT_NAME.png.sha1
@@ -0,0 +1 @@
+71b4ef1b62d61ec7fe64d09c600e069450c6fefe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_COMBOBOX.png.sha1 b/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_COMBOBOX.png.sha1
new file mode 100644
index 0000000..73deb008
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_COMBOBOX.png.sha1
@@ -0,0 +1 @@
+711a83acfa41b0724afd833b768767af4101242a
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_FEEDBACK_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_FEEDBACK_BUTTON.png.sha1
new file mode 100644
index 0000000..29d2350
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TOOLTIP_CHROMELABS_FEEDBACK_BUTTON.png.sha1
@@ -0,0 +1 @@
+cfe2539a78517d0f01576d7c1ac968f21eef7f75
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 5a30424..8718c61 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1106,6 +1106,12 @@
   <message name="IDS_SETTINGS_LANGUAGES_TRANSLATE_TARGET" desc="The label under a language's name in a list of enabled languages indicating that pages will be translated to this language.">
     This language is used when translating pages
   </message>
+  <message name="IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE" desc="Title text for the dialog informing users that the language they tried modifying is managed.">
+    Language is set by your Organization
+  </message>
+  <message name="IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY" desc="Body text for the dialog informing users that the language they tried modifying is managed.">
+    Your administrator has set a default language which cannot be modified.
+  </message>
   <if expr="chromeos">
     <message name="IDS_SETTINGS_LANGUAGES_KEYBOARD_APPS" desc="Title for the list of keyboard apps installed via Play Store by the user.">
       Keyboard apps
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY.png.sha1
new file mode 100644
index 0000000..25e6ff4
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY.png.sha1
@@ -0,0 +1 @@
+4cbd2b565b24ef0b8b0aea13015417d15cf924f6
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..d2b8fb9
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+9ded894d99a11b21d54e0463a33770b57947fc57
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 54bd0db..6f216ba 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1512,7 +1512,7 @@
      base::size(kMinimumTabWidthSettingMedium), nullptr},
     {" - tabs shrink to a large width", kMinimumTabWidthSettingLarge,
      base::size(kMinimumTabWidthSettingLarge), nullptr},
-    {" - tabs do not shrink", kMinimumTabWidthSettingFull,
+    {" - tabs don't shrink", kMinimumTabWidthSettingFull,
      base::size(kMinimumTabWidthSettingFull), nullptr}};
 
 const FeatureEntry::FeatureParam kTabHoverCardImagesOptimizationCaptureSpeed[] =
@@ -6368,9 +6368,6 @@
     {"media-app-pdf-in-ink", flag_descriptions::kMediaAppPdfInInkName,
      flag_descriptions::kMediaAppPdfInInkDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kMediaAppPdfInInk)},
-    {"os-settings-polymer3", flag_descriptions::kOsSettingsPolymer3Name,
-     flag_descriptions::kOsSettingsPolymer3Description, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kOsSettingsPolymer3)},
     {"release-notes-notification",
      flag_descriptions::kReleaseNotesNotificationName,
      flag_descriptions::kReleaseNotesNotificationDescription, kOsCrOS,
@@ -7215,13 +7212,14 @@
      FEATURE_VALUE_TYPE(ash::features::kWallpaperWebUI)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    // TODO(b/177462291): make flag available on LaCrOS.
+#if defined(OS_CHROMEOS)
+    // TODO(b/180051795): remove kOsLinux when lacros-chrome switches to
+    // kOsCrOS.
     {"enable-vaapi-av1-decode-acceleration",
      flag_descriptions::kVaapiAV1DecoderName,
-     flag_descriptions::kVaapiAV1DecoderDescription, kOsCrOS,
+     flag_descriptions::kVaapiAV1DecoderDescription, kOsCrOS | kOsLinux,
      FEATURE_VALUE_TYPE(media::kVaapiAV1Decoder)},
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_WIN) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) || \
     defined(OS_MAC)
diff --git a/chrome/browser/accessibility/image_annotation_browsertest.cc b/chrome/browser/accessibility/image_annotation_browsertest.cc
index c49e0f5..c0a15d7 100644
--- a/chrome/browser/accessibility/image_annotation_browsertest.cc
+++ b/chrome/browser/accessibility/image_annotation_browsertest.cc
@@ -262,7 +262,7 @@
     PrefService* prefs = user_prefs::UserPrefs::Get(context);
     DCHECK(prefs);
 
-    prefs->Set(language::prefs::kAcceptLanguages,
+    prefs->Set(language::prefs::kSelectedLanguages,
                base::Value(accept_languages));
   }
 
diff --git a/chrome/browser/android/compositor/compositor_view.cc b/chrome/browser/android/compositor/compositor_view.cc
index 4081e87..e8fb655 100644
--- a/chrome/browser/android/compositor/compositor_view.cc
+++ b/chrome/browser/android/compositor/compositor_view.cc
@@ -340,8 +340,11 @@
     const content::ChildProcessTerminationInfo& info) {
   LOG(WARNING) << "Child process died (type=" << data.process_type
                << ") pid=" << data.GetProcess().Pid() << ")";
-  if (base::android::BuildInfo::GetInstance()->sdk_int() <=
-          base::android::SDK_VERSION_JELLY_BEAN_MR2 &&
+
+  // On Android R surface control layers leak if GPU process crashes, so we need
+  // to re-create surface to get rid of them.
+  if (base::android::BuildInfo::GetInstance()->sdk_int() ==
+          base::android::SDK_VERSION_R &&
       data.process_type == content::PROCESS_TYPE_GPU) {
     JNIEnv* env = base::android::AttachCurrentThread();
     compositor_->SetSurface(nullptr, false);
@@ -388,4 +391,10 @@
       std::move(tracker)));
 }
 
+void CompositorView::PreserveChildSurfaceControls(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& object) {
+  compositor_->PreserveChildSurfaceControls();
+}
+
 }  // namespace android
diff --git a/chrome/browser/android/compositor/compositor_view.h b/chrome/browser/android/compositor/compositor_view.h
index df00a2f..2f9c46e0 100644
--- a/chrome/browser/android/compositor/compositor_view.h
+++ b/chrome/browser/android/compositor/compositor_view.h
@@ -112,6 +112,9 @@
       const base::android::JavaParamRef<jobject>& object);
   void OnTabChanged(JNIEnv* env,
                     const base::android::JavaParamRef<jobject>& object);
+  void PreserveChildSurfaceControls(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& object);
 
   // CompositorClient implementation:
   void RecreateSurface() override;
diff --git a/chrome/browser/android/webapk/webapk.proto b/chrome/browser/android/webapk/webapk.proto
index f8f5e0d7b..04cd004 100644
--- a/chrome/browser/android/webapk/webapk.proto
+++ b/chrome/browser/android/webapk/webapk.proto
@@ -78,10 +78,10 @@
   // no longer available.
   optional bool stale_manifest = 9;
 
-  // The reason that a WebAPK update is requested.
-  optional UpdateReason update_reason = 10;
+  // A list of all reasons why the WebAPK needs to be updated.
+  repeated UpdateReason update_reasons = 11;
 
-  reserved 4;
+  reserved 4, 10;
 }
 
 // Contains data from the Web App Manifest.
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index 38e00f6..2f15d9f 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -179,7 +179,7 @@
     const std::string& version,
     std::map<std::string, WebApkIconHasher::Icon> icon_url_to_murmur2_hash,
     bool is_manifest_stale,
-    WebApkUpdateReason update_reason) {
+    std::vector<WebApkUpdateReason> update_reasons) {
   std::unique_ptr<webapk::WebApk> webapk(new webapk::WebApk);
   webapk->set_manifest_url(shortcut_info.manifest_url.spec());
   webapk->set_requester_application_package(
@@ -189,7 +189,9 @@
   webapk->set_package_name(package_name);
   webapk->set_version(version);
   webapk->set_stale_manifest(is_manifest_stale);
-  webapk->set_update_reason(ConvertUpdateReasonToProtoEnum(update_reason));
+
+  for (auto update_reason : update_reasons)
+    webapk->add_update_reasons(ConvertUpdateReasonToProtoEnum(update_reason));
 
   webapk::WebAppManifest* web_app_manifest = webapk->mutable_manifest();
   web_app_manifest->set_name(base::UTF16ToUTF8(shortcut_info.name));
@@ -330,14 +332,14 @@
     const std::string& version,
     std::map<std::string, WebApkIconHasher::Icon> icon_url_to_murmur2_hash,
     bool is_manifest_stale,
-    WebApkUpdateReason update_reason) {
+    std::vector<WebApkUpdateReason> update_reasons) {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
   std::unique_ptr<std::string> proto = BuildProtoInBackground(
       shortcut_info, primary_icon, is_primary_icon_maskable, splash_icon,
       package_name, version, std::move(icon_url_to_murmur2_hash),
-      is_manifest_stale, update_reason);
+      is_manifest_stale, std::move(update_reasons));
 
   // Create directory if it does not exist.
   base::CreateDirectory(update_request_path.DirName());
@@ -438,7 +440,7 @@
       base::BindOnce(&BuildProtoInBackground, shortcut_info, primary_icon,
                      is_primary_icon_maskable, splash_icon, package_name,
                      version, std::move(icon_url_to_murmur2_hash),
-                     is_manifest_stale, WebApkUpdateReason::NONE),
+                     is_manifest_stale, std::vector<WebApkUpdateReason>()),
       std::move(callback));
 }
 
@@ -453,7 +455,7 @@
     const std::string& version,
     std::map<std::string, WebApkIconHasher::Icon> icon_url_to_murmur2_hash,
     bool is_manifest_stale,
-    WebApkUpdateReason update_reason,
+    std::vector<WebApkUpdateReason> update_reasons,
     base::OnceCallback<void(bool)> callback) {
   base::PostTaskAndReplyWithResult(
       GetBackgroundTaskRunner().get(), FROM_HERE,
@@ -461,7 +463,7 @@
                      shortcut_info, primary_icon, is_primary_icon_maskable,
                      splash_icon, package_name, version,
                      std::move(icon_url_to_murmur2_hash), is_manifest_stale,
-                     update_reason),
+                     std::move(update_reasons)),
       std::move(callback));
 }
 
diff --git a/chrome/browser/android/webapk/webapk_installer.h b/chrome/browser/android/webapk/webapk_installer.h
index d1a50f3a..570a121 100644
--- a/chrome/browser/android/webapk/webapk_installer.h
+++ b/chrome/browser/android/webapk/webapk_installer.h
@@ -132,7 +132,7 @@
       const std::string& version,
       std::map<std::string, WebApkIconHasher::Icon> icon_url_to_murmur2_hash,
       bool is_manifest_stale,
-      WebApkUpdateReason update_reason,
+      std::vector<WebApkUpdateReason> update_reasons,
       base::OnceCallback<void(bool)> callback);
 
  protected:
diff --git a/chrome/browser/android/webapk/webapk_installer_unittest.cc b/chrome/browser/android/webapk/webapk_installer_unittest.cc
index b5f7c97..19a160e 100644
--- a/chrome/browser/android/webapk/webapk_installer_unittest.cc
+++ b/chrome/browser/android/webapk/webapk_installer_unittest.cc
@@ -167,7 +167,7 @@
     WebApkInstaller::StoreUpdateRequestToFile(
         update_request_path, webapps::ShortcutInfo((GURL())), SkBitmap(), false,
         SkBitmap(), "", "", std::map<std::string, WebApkIconHasher::Icon>(),
-        false, WebApkUpdateReason::PRIMARY_ICON_HASH_DIFFERS,
+        false, {WebApkUpdateReason::PRIMARY_ICON_HASH_DIFFERS},
         base::BindOnce(&UpdateRequestStorer::OnComplete,
                        base::Unretained(this)));
     run_loop.Run();
diff --git a/chrome/browser/android/webapk/webapk_update_manager.cc b/chrome/browser/android/webapk/webapk_update_manager.cc
index 0633e58..92db156 100644
--- a/chrome/browser/android/webapk/webapk_update_manager.cc
+++ b/chrome/browser/android/webapk/webapk_update_manager.cc
@@ -79,7 +79,7 @@
     const JavaParamRef<jstring>& java_webapk_package,
     jint java_webapk_version,
     jboolean java_is_manifest_stale,
-    jint java_update_reason,
+    const JavaParamRef<jintArray>& java_update_reasons,
     const JavaParamRef<jobject>& java_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -195,15 +195,19 @@
     info.shortcut_items.push_back(std::move(shortcut_item));
   }
 
-  WebApkUpdateReason update_reason =
-      static_cast<WebApkUpdateReason>(java_update_reason);
+  std::vector<int> int_update_reasons;
+  base::android::JavaIntArrayToIntVector(env, java_update_reasons,
+                                         &int_update_reasons);
+  std::vector<WebApkUpdateReason> update_reasons;
+  for (int update_reason : int_update_reasons)
+    update_reasons.push_back(static_cast<WebApkUpdateReason>(update_reason));
 
   WebApkInstaller::StoreUpdateRequestToFile(
       base::FilePath(update_request_path), info, primary_icon,
       java_is_primary_icon_maskable, splash_icon, webapk_package,
       base::NumberToString(java_webapk_version),
       std::move(icon_url_to_murmur2_hash), java_is_manifest_stale,
-      update_reason,
+      std::move(update_reasons),
       base::BindOnce(&base::android::RunBooleanCallbackAndroid,
                      ScopedJavaGlobalRef<jobject>(java_callback)));
 }
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 5b17afb..dd9cbcb 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -1444,8 +1444,8 @@
     if (command_id != ash::LAUNCH_APP_SHORTCUT_FIRST) {
       AddSeparator(ui::PADDED_SEPARATOR, &menu_items);
     }
-    AddArcCommandItem(command_id++, item.shortcut_id, item.short_label,
-                      item.icon, &menu_items);
+    AddShortcutCommandItem(command_id++, item.shortcut_id, item.short_label,
+                           item.icon, &menu_items);
   }
   std::move(callback).Run(std::move(menu_items));
   arc_app_shortcuts_request_.reset();
diff --git a/chrome/browser/apps/app_service/lacros_apps.cc b/chrome/browser/apps/app_service/lacros_apps.cc
index 817c012..223cdfa2 100644
--- a/chrome/browser/apps/app_service/lacros_apps.cc
+++ b/chrome/browser/apps/app_service/lacros_apps.cc
@@ -34,6 +34,8 @@
       apps::mojom::Readiness::kReady,
       "Lacros",  // TODO(jamescook): Localized name.
       apps::mojom::InstallSource::kSystem);
+  // Make Lacros searchable with the term "chrome", too.
+  app->additional_search_terms.push_back("chrome");
   app->icon_key = NewIconKey(is_ready ? State::kReady : State::kLoading);
   app->searchable = apps::mojom::OptionalBool::kTrue;
   app->show_in_launcher = apps::mojom::OptionalBool::kTrue;
diff --git a/chrome/browser/apps/app_service/menu_util.cc b/chrome/browser/apps/app_service/menu_util.cc
index 0641317..d938b11 100644
--- a/chrome/browser/apps/app_service/menu_util.cc
+++ b/chrome/browser/apps/app_service/menu_util.cc
@@ -63,11 +63,11 @@
   (*menu_items)->items.push_back(std::move(menu_item));
 }
 
-void AddArcCommandItem(int command_id,
-                       const std::string& shortcut_id,
-                       const std::string& label,
-                       const gfx::ImageSkia& icon,
-                       apps::mojom::MenuItemsPtr* menu_items) {
+void AddShortcutCommandItem(int command_id,
+                            const std::string& shortcut_id,
+                            const std::string& label,
+                            const gfx::ImageSkia& icon,
+                            apps::mojom::MenuItemsPtr* menu_items) {
   apps::mojom::MenuItemPtr menu_item = apps::mojom::MenuItem::New();
   menu_item->type = apps::mojom::MenuItemType::kPublisherCommand;
   menu_item->command_id = command_id;
diff --git a/chrome/browser/apps/app_service/menu_util.h b/chrome/browser/apps/app_service/menu_util.h
index 50c8b217..1793e4d 100644
--- a/chrome/browser/apps/app_service/menu_util.h
+++ b/chrome/browser/apps/app_service/menu_util.h
@@ -45,12 +45,12 @@
 void AddSeparator(ui::MenuSeparatorType separator_type,
                   apps::mojom::MenuItemsPtr* menu_items);
 
-// Adds an ARC shortcut command menu item to |menu_items|.
-void AddArcCommandItem(int command_id,
-                       const std::string& shortcut_id,
-                       const std::string& label,
-                       const gfx::ImageSkia& icon,
-                       apps::mojom::MenuItemsPtr* menu_items);
+// Adds a shortcut command menu item to |menu_items|.
+void AddShortcutCommandItem(int command_id,
+                            const std::string& shortcut_id,
+                            const std::string& label,
+                            const gfx::ImageSkia& icon,
+                            apps::mojom::MenuItemsPtr* menu_items);
 
 // Adds a LAUNCH_NEW menu item to |menu_items|, and create radio items for the
 // submenu.
diff --git a/chrome/browser/apps/app_service/notifications_browsertest.cc b/chrome/browser/apps/app_service/notifications_browsertest.cc
index 8603a13..4c88db6 100644
--- a/chrome/browser/apps/app_service/notifications_browsertest.cc
+++ b/chrome/browser/apps/app_service/notifications_browsertest.cc
@@ -697,6 +697,66 @@
   ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
 }
 
+IN_PROC_BROWSER_TEST_P(WebAppBadgingTest, NotificationBeforeClearBadge) {
+  ukm::TestUkmRecorder test_recorder;
+
+  badging::BadgeManager* const badge_manager_ =
+      badging::BadgeManagerFactory::GetForProfile(profile());
+  base::SimpleTestClock clock;
+  clock.SetNow(base::Time::Now());
+  ScopedBadgingClockOverride clock_override(badge_manager_, &clock);
+
+  const std::string app_id = CreateWebApp(GetUrl1(), GetScope1());
+
+  const std::string notification_id = "notification-id";
+  const auto notification = CreateNotification(notification_id, GetOrigin());
+
+  {
+    auto metadata = std::make_unique<PersistentNotificationMetadata>();
+    metadata->service_worker_scope = GetScope1();
+    NotificationDisplayService::GetForProfile(profile())->Display(
+        NotificationHandler::Type::WEB_PERSISTENT, *notification,
+        std::move(metadata));
+  }
+
+  badge_manager_->ClearBadgeForTesting(app_id, &test_recorder);
+
+  // One day under the kBadgingOverrideLifetime threshold.
+  clock.Advance(base::TimeDelta::FromDays(13));
+
+  if (GetParam() == switches::kDesktopPWAsAttentionBadgingCrOSApiOnly ||
+      GetParam() ==
+          switches::kDesktopPWAsAttentionBadgingCrOSApiOverridesNotifications) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+
+  clock.Advance(base::TimeDelta::FromDays(2));
+
+  if (GetParam() == switches::kDesktopPWAsAttentionBadgingCrOSApiOnly ||
+      GetParam() ==
+          switches::kDesktopPWAsAttentionBadgingCrOSApiOverridesNotifications) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+
+  {
+    auto metadata = std::make_unique<PersistentNotificationMetadata>();
+    metadata->service_worker_scope = GetScope1();
+    NotificationDisplayService::GetForProfile(profile())->Display(
+        NotificationHandler::Type::WEB_PERSISTENT, *notification,
+        std::move(metadata));
+  }
+
+  if (GetParam() == switches::kDesktopPWAsAttentionBadgingCrOSApiOnly) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+}
+
 IN_PROC_BROWSER_TEST_P(WebAppBadgingTest, NotificationAfterClearBadge) {
   ukm::TestUkmRecorder test_recorder;
 
@@ -713,8 +773,11 @@
 
   badge_manager_->ClearBadgeForTesting(app_id, &test_recorder);
 
+  // One day under the kBadgingOverrideLifetime threshold.
   clock.Advance(base::TimeDelta::FromDays(13));
 
+  ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+
   {
     auto metadata = std::make_unique<PersistentNotificationMetadata>();
     metadata->service_worker_scope = GetScope1();
@@ -748,6 +811,64 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(WebAppBadgingTest, NotificationAfterShowBadge) {
+  ukm::TestUkmRecorder test_recorder;
+
+  badging::BadgeManager* const badge_manager_ =
+      badging::BadgeManagerFactory::GetForProfile(profile());
+  base::SimpleTestClock clock;
+  clock.SetNow(base::Time::Now());
+  ScopedBadgingClockOverride clock_override(badge_manager_, &clock);
+
+  const std::string app_id = CreateWebApp(GetUrl1(), GetScope1());
+
+  const std::string notification_id = "notification-id";
+  const auto notification = CreateNotification(notification_id, GetOrigin());
+
+  badge_manager_->SetBadgeForTesting(app_id, 1, &test_recorder);
+
+  // One day under the kBadgingOverrideLifetime threshold.
+  clock.Advance(base::TimeDelta::FromDays(13));
+
+  if (GetParam() ==
+      switches::kDesktopPWAsAttentionBadgingCrOSNotificationsOnly) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+
+  clock.Advance(base::TimeDelta::FromDays(2));
+
+  if (GetParam() ==
+      switches::kDesktopPWAsAttentionBadgingCrOSNotificationsOnly) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+
+  {
+    auto metadata = std::make_unique<PersistentNotificationMetadata>();
+    metadata->service_worker_scope = GetScope1();
+    NotificationDisplayService::GetForProfile(profile())->Display(
+        NotificationHandler::Type::WEB_PERSISTENT, *notification,
+        std::move(metadata));
+  }
+
+  ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+
+  NotificationDisplayService::GetForProfile(profile())->Close(
+      NotificationHandler::Type::WEB_PERSISTENT, notification_id);
+
+  if (GetParam() ==
+          switches::kDesktopPWAsAttentionBadgingCrOSNotificationsOnly ||
+      GetParam() ==
+          switches::kDesktopPWAsAttentionBadgingCrOSApiOverridesNotifications) {
+    ASSERT_EQ(OptionalBool::kFalse, HasBadge(profile(), app_id));
+  } else {
+    ASSERT_EQ(OptionalBool::kTrue, HasBadge(profile(), app_id));
+  }
+}
+
 std::string WebAppBadgingParamToString(
     const testing::TestParamInfo<WebAppBadgingTest::ParamType> params) {
   std::string result;
diff --git a/chrome/browser/apps/app_service/web_apps_chromeos.cc b/chrome/browser/apps/app_service/web_apps_chromeos.cc
index 29bc84e..c83b50d 100644
--- a/chrome/browser/apps/app_service/web_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/web_apps_chromeos.cc
@@ -344,9 +344,7 @@
 
     const std::string label = base::UTF16ToUTF8(menu_item_info.name);
 
-    // TODO(crbug.com/1140356): Rename AddArcCommandItem to
-    // AddPublisherCommandItem.
-    AddArcCommandItem(command_id, shortcut_id, label, icon, &menu_items);
+    AddShortcutCommandItem(command_id, shortcut_id, label, icon, &menu_items);
 
     ++menu_item_index;
   }
@@ -782,15 +780,14 @@
              switches::
                  kDesktopPWAsAttentionBadgingCrOSApiOverridesNotifications) {
     // When the flag is set to "api-overrides-notifications" we show a badge if
-    // either the Web Badging API has a badge set, or the Badging API has not
-    // been used by the origin and a notification is showing.
-    if (!badge_manager_)
+    // either the Web Badging API recently has a badge set, or the Badging API
+    // has not been recently used by the app and a notification is showing.
+    if (!badge_manager_ || !badge_manager_->HasRecentApiUsage(app_id))
       return has_notification;
-    if (badge_manager_->GetBadgeValue(app_id).has_value())
-      return apps::mojom::OptionalBool::kTrue;
-    if (badge_manager_->HasRecentApiUsage(app_id))
-      return apps::mojom::OptionalBool::kFalse;
-    return has_notification;
+
+    return badge_manager_->GetBadgeValue(app_id).has_value()
+               ? apps::mojom::OptionalBool::kTrue
+               : apps::mojom::OptionalBool::kFalse;
   } else {
     // Show a badge only if a notification is showing.
     return has_notification;
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher.cc b/chrome/browser/ash/borealis/borealis_app_launcher.cc
index a7e12ca..a51e1499 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher.cc
+++ b/chrome/browser/ash/borealis/borealis_app_launcher.cc
@@ -23,6 +23,13 @@
 void BorealisAppLauncher::Launch(const BorealisContext& ctx,
                                  const std::string& app_id,
                                  OnLaunchedCallback callback) {
+  Launch(std::move(ctx), std::move(app_id), {}, std::move(callback));
+}
+
+void BorealisAppLauncher::Launch(const BorealisContext& ctx,
+                                 const std::string& app_id,
+                                 const std::vector<std::string>& args,
+                                 OnLaunchedCallback callback) {
   // Do not launch anything when using the installer app.
   //
   // TODO(b/170677773): Launch a _certain_ application...
@@ -45,6 +52,9 @@
   request.set_vm_name(ctx.vm_name());
   request.set_container_name(ctx.container_name());
   request.set_desktop_file_id(reg->DesktopFileId());
+  std::copy(
+      args.begin(), args.end(),
+      google::protobuf::RepeatedFieldBackInserter(request.mutable_files()));
 
   chromeos::DBusThreadManager::Get()
       ->GetCiceroneClient()
@@ -77,6 +87,12 @@
 
 void BorealisAppLauncher::Launch(std::string app_id,
                                  OnLaunchedCallback callback) {
+  Launch(std::move(app_id), {}, std::move(callback));
+}
+
+void BorealisAppLauncher::Launch(std::string app_id,
+                                 const std::vector<std::string>& args,
+                                 OnLaunchedCallback callback) {
   DCHECK(BorealisService::GetForProfile(profile_)->Features().IsAllowed());
   if (!borealis::BorealisService::GetForProfile(profile_)
            ->Features()
@@ -90,7 +106,7 @@
     borealis::ShowBorealisSplashScreenView(profile_);
   BorealisService::GetForProfile(profile_)->ContextManager().StartBorealis(
       base::BindOnce(
-          [](std::string app_id,
+          [](std::string app_id, const std::vector<std::string>& args,
              BorealisAppLauncher::OnLaunchedCallback callback,
              BorealisContextManager::ContextOrFailure result) {
             if (!result) {
@@ -104,9 +120,9 @@
               return;
             }
             BorealisAppLauncher::Launch(*result.Value(), std::move(app_id),
-                                        std::move(callback));
+                                        std::move(args), std::move(callback));
           },
-          std::move(app_id), std::move(callback)));
+          std::move(app_id), std::move(args), std::move(callback)));
 }
 
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher.h b/chrome/browser/ash/borealis/borealis_app_launcher.h
index 98941d1..c0076dc3 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher.h
+++ b/chrome/browser/ash/borealis/borealis_app_launcher.h
@@ -33,12 +33,26 @@
                      const std::string& app_id,
                      OnLaunchedCallback callback);
 
+  // Launch the app with the given |app_id| and with the given |args| in the
+  // borealis instance referred to by |ctx|.
+  static void Launch(const BorealisContext& ctx,
+                     const std::string& app_id,
+                     const std::vector<std::string>& args,
+                     OnLaunchedCallback callback);
+
   explicit BorealisAppLauncher(Profile* profile);
 
   // Launch the given |app_id|'s associated application. This can be the
   // borealis launcher itself or one of its GuestOsRegistry apps.
   void Launch(std::string app_id, OnLaunchedCallback callback);
 
+  // Launch the given |app_id|'s associated application with the given |args|.
+  // This can be the borealis launcher itself or one of its GuestOsRegistry
+  // apps.
+  void Launch(std::string app_id,
+              const std::vector<std::string>& args,
+              OnLaunchedCallback callback);
+
  private:
   Profile* const profile_;
 };
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc b/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
index 399eca0..a9bfdab 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
@@ -156,5 +156,31 @@
   BorealisAppLauncher::Launch(Context(), baz_id, callback_check.BindOnce());
 }
 
+TEST_F(BorealisAppLauncherTest, ApplicationIsRunWithGivenArgs) {
+  CallbackFactory callback_check;
+  EXPECT_CALL(callback_check,
+              Call(BorealisAppLauncher::LaunchResult::kSuccess));
+  std::string baz_id = SetDummyApp("baz.desktop");
+  Cicerone()->SetOnLaunchContainerApplicationCallback(
+      base::BindLambdaForTesting(
+          [&](const vm_tools::cicerone::LaunchContainerApplicationRequest&
+                  request,
+              chromeos::DBusMethodCallback<
+                  vm_tools::cicerone::LaunchContainerApplicationResponse>
+                  callback) {
+            EXPECT_EQ(request.desktop_file_id(), "baz.desktop");
+            EXPECT_THAT(
+                request.files(),
+                testing::Pointwise(testing::Eq(),
+                                   {"these", "are", "some", "arguments"}));
+            vm_tools::cicerone::LaunchContainerApplicationResponse response;
+            response.set_success(true);
+            std::move(callback).Run(response);
+          }));
+  BorealisAppLauncher::Launch(Context(), baz_id,
+                              {"these", "are", "some", "arguments"},
+                              callback_check.BindOnce());
+}
+
 }  // namespace
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_context.cc b/chrome/browser/ash/borealis/borealis_context.cc
index 042f68c..8a8f8869 100644
--- a/chrome/browser/ash/borealis/borealis_context.cc
+++ b/chrome/browser/ash/borealis/borealis_context.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/scoped_observation.h"
+#include "chrome/browser/ash/borealis/borealis_game_mode_controller.h"
 #include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/borealis/borealis_shutdown_monitor.h"
@@ -57,7 +58,8 @@
       lifetime_observer_(std::make_unique<BorealisLifetimeObserver>(profile)),
       guest_os_stability_monitor_(
           std::make_unique<guest_os::GuestOsStabilityMonitor>(
-              kBorealisStabilityHistogram)) {}
+              kBorealisStabilityHistogram)),
+      game_mode_controller_(std::make_unique<BorealisGameModeController>()) {}
 
 std::unique_ptr<BorealisContext>
 BorealisContext::CreateBorealisContextForTesting(Profile* profile) {
diff --git a/chrome/browser/ash/borealis/borealis_context.h b/chrome/browser/ash/borealis/borealis_context.h
index e0a9ccbd..5c610f71 100644
--- a/chrome/browser/ash/borealis/borealis_context.h
+++ b/chrome/browser/ash/borealis/borealis_context.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/files/file_path.h"
+#include "chrome/browser/ash/borealis/borealis_game_mode_controller.h"
 
 class Profile;
 
@@ -63,6 +64,8 @@
 
   std::unique_ptr<guest_os::GuestOsStabilityMonitor>
       guest_os_stability_monitor_;
+
+  std::unique_ptr<BorealisGameModeController> game_mode_controller_;
 };
 
 }  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_game_mode_controller.cc b/chrome/browser/ash/borealis/borealis_game_mode_controller.cc
new file mode 100644
index 0000000..d66c07f
--- /dev/null
+++ b/chrome/browser/ash/borealis/borealis_game_mode_controller.cc
@@ -0,0 +1,101 @@
+// 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/ash/borealis/borealis_game_mode_controller.h"
+
+#include "ash/shell.h"
+#include "chrome/browser/ash/borealis/borealis_service.h"
+#include "chrome/browser/ash/borealis/borealis_window_manager.h"
+#include "ui/views/widget/widget.h"
+
+namespace borealis {
+
+BorealisGameModeController::BorealisGameModeController()
+    : root_focus_observer_(this) {
+  if (!ash::Shell::HasInstance())
+    return;
+  aura::client::FocusClient* focus_client =
+      aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow());
+  root_focus_observer_.Observe(focus_client);
+  // In case a window is already focused when this is constructed.
+  OnWindowFocused(focus_client->GetFocusedWindow(), nullptr);
+}
+
+BorealisGameModeController::~BorealisGameModeController() = default;
+
+void BorealisGameModeController::OnWindowFocused(aura::Window* gained_focus,
+                                                 aura::Window* lost_focus) {
+  if (!gained_focus) {
+    focused_.reset();
+    return;
+  }
+
+  auto* widget = views::Widget::GetTopLevelWidgetForNativeView(gained_focus);
+  aura::Window* window = widget->GetNativeWindow();
+  auto* window_state = ash::WindowState::Get(window);
+
+  if (window_state && BorealisWindowManager::IsBorealisWindow(window)) {
+    focused_ =
+        std::make_unique<WindowTracker>(window_state, std::move(focused_));
+  } else {
+    focused_.reset();
+  }
+}
+
+BorealisGameModeController::WindowTracker::WindowTracker(
+    ash::WindowState* window_state,
+    std::unique_ptr<WindowTracker> previous_focus) {
+  if (previous_focus && previous_focus->game_mode_) {
+    game_mode_ = std::move(previous_focus->game_mode_);
+  }
+  UpdateGameModeStatus(window_state);
+  window_state_observer_.Observe(window_state);
+  window_observer_.Observe(window_state->window());
+}
+
+BorealisGameModeController::WindowTracker::~WindowTracker() {}
+
+void BorealisGameModeController::WindowTracker::OnPostWindowStateTypeChange(
+    ash::WindowState* window_state,
+    chromeos::WindowStateType old_type) {
+  UpdateGameModeStatus(window_state);
+}
+
+void BorealisGameModeController::WindowTracker::UpdateGameModeStatus(
+    ash::WindowState* window_state) {
+  if (!game_mode_ && window_state->IsFullscreen()) {
+    game_mode_ = std::make_unique<ScopedGameMode>();
+  } else if (game_mode_ && !window_state->IsFullscreen()) {
+    game_mode_.reset();
+  }
+}
+
+void BorealisGameModeController::WindowTracker::OnWindowDestroying(
+    aura::Window* window) {
+  window_state_observer_.Reset();
+  window_observer_.Reset();
+  game_mode_.reset();
+}
+
+BorealisGameModeController::ScopedGameMode*
+BorealisGameModeController::WindowTracker::GetGameMode() {
+  return game_mode_.get();
+}
+
+BorealisGameModeController::ScopedGameMode*
+BorealisGameModeController::GetGameModeForTesting() {
+  if (focused_)
+    return focused_->GetGameMode();
+  return nullptr;
+}
+
+BorealisGameModeController::ScopedGameMode::ScopedGameMode() {
+  LOG(ERROR) << "Entering Game Mode";
+}
+
+BorealisGameModeController::ScopedGameMode::~ScopedGameMode() {
+  LOG(ERROR) << "Exiting Game Mode";
+}
+
+}  // namespace borealis
diff --git a/chrome/browser/ash/borealis/borealis_game_mode_controller.h b/chrome/browser/ash/borealis/borealis_game_mode_controller.h
new file mode 100644
index 0000000..9457615
--- /dev/null
+++ b/chrome/browser/ash/borealis/borealis_game_mode_controller.h
@@ -0,0 +1,86 @@
+// 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_ASH_BOREALIS_BOREALIS_GAME_MODE_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_BOREALIS_BOREALIS_GAME_MODE_CONTROLLER_H_
+
+#include "ash/wm/window_state.h"
+#include "ash/wm/window_state_observer.h"
+#include "base/scoped_observation.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/client/focus_client.h"
+
+namespace borealis {
+
+// When a borealis window enters full screen, game mode is enabled.
+// The controller works as follows:
+//
+//          +"GameMode off"+              "GameMode off"
+//          |              |                  |     ^ Not fullscreen
+//          |              | N                |     |
+//          V   focused    |              Y   V     |   Fullscreen
+// "Watch focus"------->"Borealis window?"-->"Watch state"----->"GameMode on"
+//         ^                    ^             |   |     ^          |
+//         |                    +-------------+   |     +----------+
+//         |                    focus changed     |
+//         +------"GameMode off"<-----------------+
+//                                No window focused
+//
+class BorealisGameModeController : public aura::client::FocusChangeObserver {
+ public:
+  BorealisGameModeController();
+  BorealisGameModeController(const BorealisGameModeController&) = delete;
+  BorealisGameModeController& operator=(const BorealisGameModeController&) =
+      delete;
+  ~BorealisGameModeController() override;
+
+  // Overridden from FocusChangeObserver
+  void OnWindowFocused(aura::Window* gained_focus,
+                       aura::Window* lost_focus) override;
+
+  // TODO(b/179961266) replace with sending actual messages to enter game mode.
+  class ScopedGameMode {
+   public:
+    ScopedGameMode();
+    ~ScopedGameMode();
+  };
+
+  class WindowTracker : public ash::WindowStateObserver,
+                        public aura::WindowObserver {
+   public:
+    WindowTracker(ash::WindowState* window_state,
+                  std::unique_ptr<WindowTracker> previous_focus);
+    ~WindowTracker() override;
+
+    // Overridden from WindowObserver
+    void OnWindowDestroying(aura::Window* window) override;
+
+    // Overridden from WindowStateObserver
+    void OnPostWindowStateTypeChange(
+        ash::WindowState* window_state,
+        chromeos::WindowStateType old_type) override;
+
+    BorealisGameModeController::ScopedGameMode* GetGameMode();
+    void UpdateGameModeStatus(ash::WindowState* window_state);
+
+   private:
+    base::ScopedObservation<ash::WindowState, ash::WindowStateObserver>
+        window_state_observer_{this};
+    base::ScopedObservation<aura::Window, aura::WindowObserver>
+        window_observer_{this};
+    std::unique_ptr<BorealisGameModeController::ScopedGameMode> game_mode_;
+  };
+
+  ScopedGameMode* GetGameModeForTesting();
+
+ private:
+  base::ScopedObservation<aura::client::FocusClient,
+                          aura::client::FocusChangeObserver>
+      root_focus_observer_;
+  std::unique_ptr<WindowTracker> focused_ = nullptr;
+};
+
+}  // namespace borealis
+
+#endif  // CHROME_BROWSER_ASH_BOREALIS_BOREALIS_GAME_MODE_CONTROLLER_H_
diff --git a/chrome/browser/ash/borealis/borealis_game_mode_controller_unittest.cc b/chrome/browser/ash/borealis/borealis_game_mode_controller_unittest.cc
new file mode 100644
index 0000000..1246fc94
--- /dev/null
+++ b/chrome/browser/ash/borealis/borealis_game_mode_controller_unittest.cc
@@ -0,0 +1,131 @@
+// 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/ash/borealis/borealis_game_mode_controller.h"
+
+#include "ash/test/test_widget_builder.h"
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/ash/borealis/borealis_features.h"
+#include "chrome/browser/ash/borealis/borealis_service_fake.h"
+#include "chrome/browser/ash/borealis/borealis_window_manager.h"
+#include "chrome/test/base/chrome_ash_test_base.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/exo/shell_surface_util.h"
+#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
+#include "ui/wm/core/window_util.h"
+
+namespace borealis {
+namespace {
+
+class BorealisGameModeControllerTest : public ChromeAshTestBase {
+ protected:
+  void SetUp() override {
+    ChromeAshTestBase::SetUp();
+    profile_ = std::make_unique<TestingProfile>();
+    service_fake_ = BorealisServiceFake::UseFakeForTesting(profile_.get());
+    window_manager_ = std::make_unique<BorealisWindowManager>(profile_.get());
+    service_fake_->SetWindowManagerForTesting(window_manager_.get());
+    game_mode_controller_ = std::make_unique<BorealisGameModeController>();
+    features_ = std::make_unique<BorealisFeatures>(profile_.get());
+    service_fake_->SetFeaturesForTesting(features_.get());
+  }
+
+  void TearDown() override {
+    game_mode_controller_.reset();
+    ChromeAshTestBase::TearDown();
+  }
+
+  std::unique_ptr<views::Widget> CreateTestWidget(std::string name,
+                                                  bool fullscreen = false) {
+    ash::TestWidgetBuilder builder;
+    builder.SetShow(false);
+    std::unique_ptr<views::Widget> widget = builder.BuildOwnsNativeWidget();
+    exo::SetShellApplicationId(widget->GetNativeWindow(), name);
+    if (fullscreen) {
+      widget->SetFullscreen(true);
+    }
+    widget->Show();
+    return widget;
+  }
+
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<BorealisWindowManager> window_manager_;
+  std::unique_ptr<BorealisGameModeController> game_mode_controller_;
+  std::unique_ptr<BorealisFeatures> features_;
+  BorealisServiceFake* service_fake_;
+};
+
+TEST_F(BorealisGameModeControllerTest, ChangingFullScreenTogglesGameMode) {
+  std::unique_ptr<views::Widget> test_widget =
+      CreateTestWidget("org.chromium.borealis.foo", true);
+  aura::Window* window = test_widget->GetNativeWindow();
+  EXPECT_TRUE(ash::WindowState::Get(window)->IsFullscreen());
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+  test_widget->SetFullscreen(false);
+  EXPECT_FALSE(ash::WindowState::Get(window)->IsFullscreen());
+  EXPECT_EQ(nullptr, game_mode_controller_->GetGameModeForTesting());
+}
+
+TEST_F(BorealisGameModeControllerTest, NonBorealisWindowDoesNotEnterGameMode) {
+  std::unique_ptr<aura::Window> window = CreateTestWindow();
+  views::Widget::GetTopLevelWidgetForNativeView(window.get())
+      ->SetFullscreen(true);
+  EXPECT_TRUE(ash::WindowState::Get(window.get())->IsFullscreen());
+  EXPECT_EQ(nullptr, game_mode_controller_->GetGameModeForTesting());
+}
+
+TEST_F(BorealisGameModeControllerTest, SwitchingWindowsTogglesGameMode) {
+  std::unique_ptr<views::Widget> test_widget =
+      CreateTestWidget("org.chromium.borealis.foo", true);
+  aura::Window* window = test_widget->GetNativeWindow();
+  EXPECT_TRUE(ash::WindowState::Get(window)->IsFullscreen());
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+
+  std::unique_ptr<views::Widget> other_test_widget =
+      CreateTestWidget("org.chromium.borealis.bar");
+  aura::Window* other_window = other_test_widget->GetNativeWindow();
+
+  EXPECT_TRUE(other_window->HasFocus());
+  EXPECT_EQ(nullptr, game_mode_controller_->GetGameModeForTesting());
+
+  window->Focus();
+
+  EXPECT_TRUE(ash::WindowState::Get(window)->IsFullscreen());
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+}
+
+TEST_F(BorealisGameModeControllerTest, DestroyingWindowExitsGameMode) {
+  std::unique_ptr<views::Widget> test_widget =
+      CreateTestWidget("org.chromium.borealis.foo", true);
+  aura::Window* window = test_widget->GetNativeWindow();
+  EXPECT_TRUE(ash::WindowState::Get(window)->IsFullscreen());
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+
+  test_widget.reset();
+
+  EXPECT_EQ(nullptr, game_mode_controller_->GetGameModeForTesting());
+}
+
+TEST_F(BorealisGameModeControllerTest, SwitchingWindowsMaintainsGameMode) {
+  std::unique_ptr<views::Widget> test_widget =
+      CreateTestWidget("org.chromium.borealis.foo", true);
+  aura::Window* window = test_widget->GetNativeWindow();
+  BorealisGameModeController::ScopedGameMode* game_mode =
+      game_mode_controller_->GetGameModeForTesting();
+  EXPECT_NE(nullptr, game_mode);
+
+  std::unique_ptr<views::Widget> other_test_widget =
+      CreateTestWidget("org.chromium.borealis.foo", true);
+
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+  EXPECT_EQ(game_mode, game_mode_controller_->GetGameModeForTesting());
+
+  window->Focus();
+  EXPECT_NE(nullptr, game_mode_controller_->GetGameModeForTesting());
+  EXPECT_EQ(game_mode, game_mode_controller_->GetGameModeForTesting());
+}
+
+}  // namespace
+}  // namespace borealis
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 6251c44..1e70045 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -40,6 +40,7 @@
 #include "components/user_manager/user_manager.h"
 #include "components/user_manager/user_type.h"
 #include "components/version_info/channel.h"
+#include "media/capture/mojom/video_capture.mojom.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/system/invitation.h"
 
@@ -193,7 +194,7 @@
 
 base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
   static_assert(
-      crosapi::mojom::Crosapi::Version_ == 17,
+      crosapi::mojom::Crosapi::Version_ == 18,
       "if you add a new crosapi, please add it to the version map here");
   InterfaceVersions versions;
   AddVersion<chromeos::sensors::mojom::SensorHalClient>(&versions);
@@ -216,6 +217,7 @@
   AddVersion<crosapi::mojom::SnapshotCapturer>(&versions);
   AddVersion<crosapi::mojom::TestController>(&versions);
   AddVersion<crosapi::mojom::UrlHandler>(&versions);
+  AddVersion<crosapi::mojom::VideoCaptureDeviceFactory>(&versions);
   AddVersion<device::mojom::HidConnection>(&versions);
   AddVersion<device::mojom::HidManager>(&versions);
   AddVersion<media_session::mojom::MediaControllerManager>(&versions);
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.cc b/chrome/browser/ash/crosapi/crosapi_ash.cc
index 0d897ad..e6a3368 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.cc
+++ b/chrome/browser/ash/crosapi/crosapi_ash.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/ash/crosapi/select_file_ash.h"
 #include "chrome/browser/ash/crosapi/test_controller_ash.h"
 #include "chrome/browser/ash/crosapi/url_handler_ash.h"
+#include "chrome/browser/ash/crosapi/video_capture_device_factory_ash.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
@@ -65,7 +66,9 @@
       screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
       select_file_ash_(std::make_unique<SelectFileAsh>()),
       test_controller_ash_(std::make_unique<TestControllerAsh>()),
-      url_handler_ash_(std::make_unique<UrlHandlerAsh>()) {
+      url_handler_ash_(std::make_unique<UrlHandlerAsh>()),
+      video_capture_device_factory_ash_(
+          std::make_unique<VideoCaptureDeviceFactoryAsh>()) {
   receiver_set_.set_disconnect_handler(base::BindRepeating(
       &CrosapiAsh::OnDisconnected, weak_factory_.GetWeakPtr()));
 }
@@ -228,6 +231,11 @@
       ->BindMachineLearningService(std::move(receiver));
 }
 
+void CrosapiAsh::BindVideoCaptureDeviceFactory(
+    mojo::PendingReceiver<mojom::VideoCaptureDeviceFactory> receiver) {
+  video_capture_device_factory_ash_->BindReceiver(std::move(receiver));
+}
+
 void CrosapiAsh::OnBrowserStartup(mojom::BrowserInfoPtr browser_info) {
   BrowserManager::Get()->set_browser_version(browser_info->browser_version);
 }
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.h b/chrome/browser/ash/crosapi/crosapi_ash.h
index ac15958..9c11fdf6 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.h
+++ b/chrome/browser/ash/crosapi/crosapi_ash.h
@@ -32,6 +32,7 @@
 class SelectFileAsh;
 class TestControllerAsh;
 class UrlHandlerAsh;
+class VideoCaptureDeviceFactoryAsh;
 
 // Implementation of Crosapi in Ash. It provides a set of APIs that
 // crosapi clients, such as lacros-chrome, can call into.
@@ -95,6 +96,9 @@
       mojo::PendingReceiver<
           chromeos::machine_learning::mojom::MachineLearningService> receiver)
       override;
+  void BindVideoCaptureDeviceFactory(
+      mojo::PendingReceiver<mojom::VideoCaptureDeviceFactory> receiver)
+      override;
 
   BrowserServiceHostAsh* browser_service_host_ash() {
     return browser_service_host_ash_.get();
@@ -119,6 +123,8 @@
   std::unique_ptr<SelectFileAsh> select_file_ash_;
   std::unique_ptr<TestControllerAsh> test_controller_ash_;
   std::unique_ptr<UrlHandlerAsh> url_handler_ash_;
+  std::unique_ptr<VideoCaptureDeviceFactoryAsh>
+      video_capture_device_factory_ash_;
 
   mojo::ReceiverSet<mojom::Crosapi, CrosapiId> receiver_set_;
   std::map<mojo::ReceiverId, base::OnceClosure> disconnect_handler_map_;
diff --git a/chrome/browser/ash/crosapi/test_mojo_connection_manager_unittest.cc b/chrome/browser/ash/crosapi/test_mojo_connection_manager_unittest.cc
index a71d9a06..20196a7 100644
--- a/chrome/browser/ash/crosapi/test_mojo_connection_manager_unittest.cc
+++ b/chrome/browser/ash/crosapi/test_mojo_connection_manager_unittest.cc
@@ -37,6 +37,7 @@
 #include "chromeos/startup/startup_switches.h"
 #include "components/account_id/account_id.h"
 #include "components/user_manager/fake_user_manager.h"
+#include "content/public/test/browser_task_environment.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
@@ -159,7 +160,7 @@
 
   // Use IO type to support the FileDescriptorWatcher API on POSIX.
   // TestingProfileManager instantiated below requires a TaskRunner.
-  base::test::TaskEnvironment task_environment{
+  content::BrowserTaskEnvironment task_environment{
       base::test::TaskEnvironment::MainThreadType::IO};
 
   chromeos::LoginState::Initialize();
diff --git a/chrome/browser/ash/crosapi/video_capture_device_ash.cc b/chrome/browser/ash/crosapi/video_capture_device_ash.cc
new file mode 100644
index 0000000..99291d1
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_capture_device_ash.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 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/ash/crosapi/video_capture_device_ash.h"
+
+#include <memory>
+#include <utility>
+
+#include "chrome/browser/ash/crosapi/video_frame_handler_ash.h"
+
+namespace crosapi {
+
+VideoCaptureDeviceAsh::VideoCaptureDeviceAsh(
+    mojo::PendingReceiver<crosapi::mojom::VideoCaptureDevice> proxy_receiver,
+    mojo::PendingRemote<video_capture::mojom::Device> device_remote,
+    base::OnceClosure cleanup_callback)
+    : device_(std::move(device_remote)) {
+  receiver_.Bind(std::move(proxy_receiver));
+  receiver_.set_disconnect_handler(std::move(cleanup_callback));
+}
+
+VideoCaptureDeviceAsh::~VideoCaptureDeviceAsh() = default;
+
+void VideoCaptureDeviceAsh::Start(
+    const media::VideoCaptureParams& requested_settings,
+    mojo::PendingRemote<crosapi::mojom::VideoFrameHandler> proxy_handler) {
+  mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> handler_remote;
+  handler_ = std::make_unique<VideoFrameHandlerAsh>(
+      handler_remote.InitWithNewPipeAndPassReceiver(),
+      std::move(proxy_handler));
+  device_->Start(std::move(requested_settings), std::move(handler_remote));
+}
+
+void VideoCaptureDeviceAsh::MaybeSuspend() {
+  device_->MaybeSuspend();
+}
+
+void VideoCaptureDeviceAsh::Resume() {
+  device_->Resume();
+}
+
+void VideoCaptureDeviceAsh::GetPhotoState(GetPhotoStateCallback callback) {
+  device_->GetPhotoState(std::move(callback));
+}
+
+void VideoCaptureDeviceAsh::SetPhotoOptions(
+    media::mojom::PhotoSettingsPtr settings,
+    SetPhotoOptionsCallback callback) {
+  device_->SetPhotoOptions(std::move(settings), std::move(callback));
+}
+
+void VideoCaptureDeviceAsh::TakePhoto(TakePhotoCallback callback) {
+  device_->TakePhoto(std::move(callback));
+}
+
+void VideoCaptureDeviceAsh::ProcessFeedback(
+    const media::VideoFrameFeedback& feedback) {
+  device_->ProcessFeedback(std::move(feedback));
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/video_capture_device_ash.h b/chrome/browser/ash/crosapi/video_capture_device_ash.h
new file mode 100644
index 0000000..06dc3b66
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_capture_device_ash.h
@@ -0,0 +1,54 @@
+// Copyright 2021 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_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_ASH_H_
+
+#include "base/callback_forward.h"
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
+
+namespace crosapi {
+
+class VideoFrameHandlerAsh;
+
+// It is used as a proxy to communicate between Lacros-Chrome and real
+// video_capture::Device.
+class VideoCaptureDeviceAsh : public crosapi::mojom::VideoCaptureDevice {
+ public:
+  VideoCaptureDeviceAsh(
+      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDevice> proxy_receiver,
+      mojo::PendingRemote<video_capture::mojom::Device> device_remote,
+      base::OnceClosure cleanup_callback);
+  VideoCaptureDeviceAsh(const VideoCaptureDeviceAsh&) = delete;
+  VideoCaptureDeviceAsh& operator=(const VideoCaptureDeviceAsh&) = delete;
+  ~VideoCaptureDeviceAsh() override;
+
+ private:
+  // crosapi::mojom::Device implementation.
+  void Start(const media::VideoCaptureParams& requested_settings,
+             mojo::PendingRemote<crosapi::mojom::VideoFrameHandler>
+                 proxy_handler) override;
+  void MaybeSuspend() override;
+  void Resume() override;
+  void GetPhotoState(GetPhotoStateCallback callback) override;
+  void SetPhotoOptions(media::mojom::PhotoSettingsPtr settings,
+                       SetPhotoOptionsCallback callback) override;
+  void TakePhoto(TakePhotoCallback callback) override;
+  void ProcessFeedback(const media::VideoFrameFeedback& feedback) override;
+
+  std::unique_ptr<VideoFrameHandlerAsh> handler_;
+
+  mojo::Receiver<crosapi::mojom::VideoCaptureDevice> receiver_{this};
+
+  mojo::Remote<video_capture::mojom::Device> device_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_ASH_H_
diff --git a/chrome/browser/ash/crosapi/video_capture_device_factory_ash.cc b/chrome/browser/ash/crosapi/video_capture_device_factory_ash.cc
new file mode 100644
index 0000000..7678d55
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_capture_device_factory_ash.cc
@@ -0,0 +1,81 @@
+// Copyright 2021 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/ash/crosapi/video_capture_device_factory_ash.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/notreached.h"
+#include "chrome/browser/ash/crosapi/video_capture_device_ash.h"
+#include "content/public/browser/video_capture_service.h"
+
+namespace crosapi {
+
+VideoCaptureDeviceFactoryAsh::VideoCaptureDeviceFactoryAsh() {
+  content::GetVideoCaptureService().ConnectToDeviceFactory(
+      device_factory_.BindNewPipeAndPassReceiver());
+}
+
+VideoCaptureDeviceFactoryAsh::~VideoCaptureDeviceFactoryAsh() = default;
+
+void VideoCaptureDeviceFactoryAsh::BindReceiver(
+    mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+void VideoCaptureDeviceFactoryAsh::GetDeviceInfos(
+    GetDeviceInfosCallback callback) {
+  device_factory_->GetDeviceInfos(std::move(callback));
+}
+
+void VideoCaptureDeviceFactoryAsh::CreateDevice(
+    const std::string& device_id,
+    mojo::PendingReceiver<crosapi::mojom::VideoCaptureDevice> device_receiver,
+    CreateDeviceCallback callback) {
+  mojo::PendingRemote<video_capture::mojom::Device> proxy_remote;
+  auto proxy_receiver = proxy_remote.InitWithNewPipeAndPassReceiver();
+  auto device_proxy = std::make_unique<VideoCaptureDeviceAsh>(
+      std::move(device_receiver), std::move(proxy_remote),
+      base::BindOnce(
+          &VideoCaptureDeviceFactoryAsh::OnClientConnectionErrorOrClose,
+          base::Unretained(this), device_id));
+
+  auto wrapped_callback = base::BindOnce(
+      [](CreateDeviceCallback callback,
+         video_capture::mojom::DeviceAccessResultCode code) {
+        crosapi::mojom::DeviceAccessResultCode crosapi_result_code;
+        switch (code) {
+          case video_capture::mojom::DeviceAccessResultCode::NOT_INITIALIZED:
+            crosapi_result_code =
+                crosapi::mojom::DeviceAccessResultCode::NOT_INITIALIZED;
+            break;
+          case video_capture::mojom::DeviceAccessResultCode::SUCCESS:
+            crosapi_result_code =
+                crosapi::mojom::DeviceAccessResultCode::SUCCESS;
+            break;
+          case video_capture::mojom::DeviceAccessResultCode::
+              ERROR_DEVICE_NOT_FOUND:
+            crosapi_result_code =
+                crosapi::mojom::DeviceAccessResultCode::ERROR_DEVICE_NOT_FOUND;
+            break;
+          default:
+            NOTREACHED() << "Unexpected device access result code";
+        }
+        std::move(callback).Run(crosapi_result_code);
+      },
+      std::move(callback));
+  devices_.emplace(device_id, std::move(device_proxy));
+  device_factory_->CreateDevice(device_id, std::move(proxy_receiver),
+                                std::move(wrapped_callback));
+}
+
+void VideoCaptureDeviceFactoryAsh::OnClientConnectionErrorOrClose(
+    const std::string& device_id) {
+  devices_.erase(device_id);
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/video_capture_device_factory_ash.h b/chrome/browser/ash/crosapi/video_capture_device_factory_ash.h
new file mode 100644
index 0000000..e3cfc01
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_capture_device_factory_ash.h
@@ -0,0 +1,62 @@
+// Copyright 2021 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_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_FACTORY_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_FACTORY_ASH_H_
+
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/public/mojom/device_factory.mojom.h"
+
+namespace crosapi {
+
+class VideoCaptureDeviceAsh;
+
+// This class is the ash-chrome implementation of the VideoCaptureDeviceFactory
+// interface. This class must only be used from the main thread.
+// It is used as a proxy between Lacros-Chrome and actual
+// video_capture::DeviceFactory in Ash-Chrome and also responsible for
+// translating structures between crosapi and other components.
+// (e.g. gfx, media, video_capture)
+class VideoCaptureDeviceFactoryAsh
+    : public crosapi::mojom::VideoCaptureDeviceFactory {
+ public:
+  VideoCaptureDeviceFactoryAsh();
+  VideoCaptureDeviceFactoryAsh(const VideoCaptureDeviceFactoryAsh&) = delete;
+  VideoCaptureDeviceFactoryAsh& operator=(const VideoCaptureDeviceFactoryAsh&) =
+      delete;
+  ~VideoCaptureDeviceFactoryAsh() override;
+
+  void BindReceiver(
+      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory>
+          receiver);
+
+  // crosapi::mojom::VideoCaptureDeviceFactory:
+  void GetDeviceInfos(GetDeviceInfosCallback callback) override;
+  void CreateDevice(
+      const std::string& device_id,
+      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDevice> device_receiver,
+      CreateDeviceCallback callback) override;
+
+ private:
+  // It will be triggered once the connection to the client of
+  // video_capture::mojom::Device in Lacros-Chrome is dropped.
+  void OnClientConnectionErrorOrClose(const std::string& device_id);
+
+  mojo::Remote<video_capture::mojom::DeviceFactory> device_factory_;
+
+  // The key is the device id used in blink::MediaStreamDevice.
+  base::flat_map<std::string, std::unique_ptr<VideoCaptureDeviceAsh>> devices_;
+
+  mojo::ReceiverSet<crosapi::mojom::VideoCaptureDeviceFactory> receivers_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_VIDEO_CAPTURE_DEVICE_FACTORY_ASH_H_
diff --git a/chrome/browser/ash/crosapi/video_frame_handler_ash.cc b/chrome/browser/ash/crosapi/video_frame_handler_ash.cc
new file mode 100644
index 0000000..f6b1b5f
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_frame_handler_ash.cc
@@ -0,0 +1,177 @@
+// Copyright 2021 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/ash/crosapi/video_frame_handler_ash.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/notreached.h"
+#include "media/base/video_transformation.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "mojo/public/cpp/system/buffer.h"
+#include "ui/gfx/gpu_memory_buffer.h"
+
+namespace crosapi {
+
+namespace {
+
+crosapi::mojom::ReadyFrameInBufferPtr ToCrosapiBuffer(
+    video_capture::mojom::ReadyFrameInBufferPtr buffer) {
+  auto crosapi_buffer = crosapi::mojom::ReadyFrameInBuffer::New();
+  crosapi_buffer->buffer_id = buffer->buffer_id;
+  crosapi_buffer->frame_feedback_id = buffer->frame_feedback_id;
+
+  mojo::PendingRemote<crosapi::mojom::ScopedAccessPermission> access_permission;
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<VideoFrameHandlerAsh::AccessPermissionProxy>(
+          std::move(buffer->access_permission)),
+      access_permission.InitWithNewPipeAndPassReceiver());
+  crosapi_buffer->access_permission = std::move(access_permission);
+
+  const auto& buffer_info = buffer->frame_info;
+  auto crosapi_buffer_info = crosapi::mojom::VideoFrameInfo::New();
+  crosapi_buffer_info->timestamp = buffer_info->timestamp;
+  crosapi_buffer_info->pixel_format = buffer_info->pixel_format;
+  crosapi_buffer_info->coded_size = buffer_info->coded_size;
+  crosapi_buffer_info->visible_rect = buffer_info->visible_rect;
+
+  auto transformation = buffer_info->metadata.transformation;
+  if (transformation) {
+    crosapi::mojom::VideoRotation crosapi_rotation;
+    switch (transformation->rotation) {
+      case media::VideoRotation::VIDEO_ROTATION_0:
+        crosapi_rotation = crosapi::mojom::VideoRotation::kVideoRotation0;
+        break;
+      case media::VideoRotation::VIDEO_ROTATION_90:
+        crosapi_rotation = crosapi::mojom::VideoRotation::kVideoRotation90;
+        break;
+      case media::VideoRotation::VIDEO_ROTATION_180:
+        crosapi_rotation = crosapi::mojom::VideoRotation::kVideoRotation180;
+        break;
+      case media::VideoRotation::VIDEO_ROTATION_270:
+        crosapi_rotation = crosapi::mojom::VideoRotation::kVideoRotation270;
+        break;
+      default:
+        NOTREACHED() << "Unexpected rotation in video frame metadata";
+    }
+    crosapi_buffer_info->rotation = crosapi_rotation;
+  }
+  if (buffer_info->metadata.reference_time.has_value())
+    crosapi_buffer_info->reference_time = *buffer_info->metadata.reference_time;
+
+  crosapi_buffer->frame_info = std::move(crosapi_buffer_info);
+  return crosapi_buffer;
+}
+
+crosapi::mojom::GpuMemoryBufferHandlePtr ToCrosapiGpuMemoryBufferHandle(
+    gfx::GpuMemoryBufferHandle buffer_handle) {
+  auto crosapi_gpu_handle = crosapi::mojom::GpuMemoryBufferHandle::New();
+  crosapi_gpu_handle->id = buffer_handle.id.id;
+  crosapi_gpu_handle->offset = buffer_handle.offset;
+  crosapi_gpu_handle->stride = buffer_handle.stride;
+
+  if (buffer_handle.type == gfx::GpuMemoryBufferType::SHARED_MEMORY_BUFFER) {
+    auto crosapi_platform_handle =
+        crosapi::mojom::GpuMemoryBufferPlatformHandle::New();
+    crosapi_platform_handle->set_shared_memory_handle(
+        std::move(buffer_handle.region));
+    crosapi_gpu_handle->platform_handle = std::move(crosapi_platform_handle);
+  } else if (buffer_handle.type == gfx::GpuMemoryBufferType::NATIVE_PIXMAP) {
+    auto crosapi_platform_handle =
+        crosapi::mojom::GpuMemoryBufferPlatformHandle::New();
+    auto crosapi_native_pixmap_handle =
+        crosapi::mojom::NativePixmapHandle::New();
+    crosapi_native_pixmap_handle->planes =
+        std::move(buffer_handle.native_pixmap_handle.planes);
+    crosapi_native_pixmap_handle->modifier =
+        buffer_handle.native_pixmap_handle.modifier;
+    crosapi_platform_handle->set_native_pixmap_handle(
+        std::move(crosapi_native_pixmap_handle));
+    crosapi_gpu_handle->platform_handle = std::move(crosapi_platform_handle);
+  }
+  return crosapi_gpu_handle;
+}
+
+}  // namespace
+
+VideoFrameHandlerAsh::VideoFrameHandlerAsh(
+    mojo::PendingReceiver<video_capture::mojom::VideoFrameHandler>
+        handler_receiver,
+    mojo::PendingRemote<crosapi::mojom::VideoFrameHandler> proxy_remote)
+    : proxy_(std::move(proxy_remote)) {
+  receiver_.Bind(std::move(handler_receiver));
+}
+
+VideoFrameHandlerAsh::~VideoFrameHandlerAsh() = default;
+
+VideoFrameHandlerAsh::AccessPermissionProxy::AccessPermissionProxy(
+    mojo::PendingRemote<video_capture::mojom::ScopedAccessPermission> remote)
+    : remote_(std::move(remote)) {}
+
+VideoFrameHandlerAsh::AccessPermissionProxy::~AccessPermissionProxy() = default;
+
+void VideoFrameHandlerAsh::OnNewBuffer(
+    int buffer_id,
+    media::mojom::VideoBufferHandlePtr buffer_handle) {
+  crosapi::mojom::VideoBufferHandlePtr crosapi_handle =
+      crosapi::mojom::VideoBufferHandle::New();
+
+  if (buffer_handle->is_shared_buffer_handle()) {
+    crosapi_handle->set_shared_buffer_handle(
+        buffer_handle->get_shared_buffer_handle()->Clone(
+            mojo::SharedBufferHandle::AccessMode::READ_WRITE));
+  } else if (buffer_handle->is_gpu_memory_buffer_handle()) {
+    crosapi_handle->set_gpu_memory_buffer_handle(ToCrosapiGpuMemoryBufferHandle(
+        std::move(buffer_handle->get_gpu_memory_buffer_handle())));
+  } else {
+    NOTREACHED() << "Unexpected new buffer type";
+  }
+  proxy_->OnNewBuffer(buffer_id, std::move(crosapi_handle));
+}
+
+void VideoFrameHandlerAsh::OnFrameReadyInBuffer(
+    video_capture::mojom::ReadyFrameInBufferPtr buffer,
+    std::vector<video_capture::mojom::ReadyFrameInBufferPtr> scaled_buffers) {
+  crosapi::mojom::ReadyFrameInBufferPtr crosapi_buffer =
+      ToCrosapiBuffer(std::move(buffer));
+  std::vector<crosapi::mojom::ReadyFrameInBufferPtr> crosapi_scaled_buffers;
+  for (auto& b : scaled_buffers)
+    crosapi_scaled_buffers.push_back(ToCrosapiBuffer(std::move(b)));
+
+  proxy_->OnFrameReadyInBuffer(std::move(crosapi_buffer),
+                               std::move(crosapi_scaled_buffers));
+}
+
+void VideoFrameHandlerAsh::OnBufferRetired(int buffer_id) {
+  proxy_->OnBufferRetired(buffer_id);
+}
+
+void VideoFrameHandlerAsh::OnError(media::VideoCaptureError error) {
+  proxy_->OnError(error);
+}
+
+void VideoFrameHandlerAsh::OnFrameDropped(
+    media::VideoCaptureFrameDropReason reason) {
+  proxy_->OnFrameDropped(reason);
+}
+
+void VideoFrameHandlerAsh::OnLog(const std::string& message) {
+  proxy_->OnLog(message);
+}
+
+void VideoFrameHandlerAsh::OnStarted() {
+  proxy_->OnStarted();
+}
+
+void VideoFrameHandlerAsh::OnStartedUsingGpuDecode() {
+  proxy_->OnStartedUsingGpuDecode();
+}
+
+void VideoFrameHandlerAsh::OnStopped() {
+  proxy_->OnStopped();
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/video_frame_handler_ash.h b/chrome/browser/ash/crosapi/video_frame_handler_ash.h
new file mode 100644
index 0000000..3eff969
--- /dev/null
+++ b/chrome/browser/ash/crosapi/video_frame_handler_ash.h
@@ -0,0 +1,73 @@
+// Copyright 2021 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_ASH_CROSAPI_VIDEO_FRAME_HANDLER_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_VIDEO_FRAME_HANDLER_ASH_H_
+
+#include <vector>
+
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "media/capture/mojom/video_capture_buffer.mojom.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
+
+namespace crosapi {
+
+// It is used as a proxy to communicate between actual VideoFrameHandler on
+// Lacros-Chrome and the actual video capture device on Ash-Chrome. Since we
+// have simplified some structures in crosapi video capture interface to reduce
+// dependencies to other components, this class should also be responsible for
+// translating those structures between the interfaces.
+class VideoFrameHandlerAsh : public video_capture::mojom::VideoFrameHandler {
+ public:
+  VideoFrameHandlerAsh(
+      mojo::PendingReceiver<video_capture::mojom::VideoFrameHandler>
+          handler_receiver,
+      mojo::PendingRemote<crosapi::mojom::VideoFrameHandler> proxy_remote);
+  VideoFrameHandlerAsh(const VideoFrameHandlerAsh&) = delete;
+  VideoFrameHandlerAsh& operator=(const VideoFrameHandlerAsh&) = delete;
+  ~VideoFrameHandlerAsh() override;
+
+  class AccessPermissionProxy : public crosapi::mojom::ScopedAccessPermission {
+   public:
+    AccessPermissionProxy(
+        mojo::PendingRemote<video_capture::mojom::ScopedAccessPermission>
+            remote);
+    AccessPermissionProxy(const AccessPermissionProxy&) = delete;
+    AccessPermissionProxy& operator=(const AccessPermissionProxy&) = delete;
+    ~AccessPermissionProxy() override;
+
+   private:
+    mojo::Remote<video_capture::mojom::ScopedAccessPermission> remote_;
+  };
+
+ private:
+  // video_capture::mojom::VideoFrameHandler implementation.
+  void OnNewBuffer(int buffer_id,
+                   media::mojom::VideoBufferHandlePtr buffer_handle) override;
+  void OnFrameReadyInBuffer(
+      video_capture::mojom::ReadyFrameInBufferPtr buffer,
+      std::vector<video_capture::mojom::ReadyFrameInBufferPtr> scaled_buffers)
+      override;
+  void OnBufferRetired(int buffer_id) override;
+  void OnError(media::VideoCaptureError error) override;
+  void OnFrameDropped(media::VideoCaptureFrameDropReason reason) override;
+  void OnLog(const std::string& message) override;
+  void OnStarted() override;
+  void OnStartedUsingGpuDecode() override;
+  void OnStopped() override;
+
+  mojo::Receiver<video_capture::mojom::VideoFrameHandler> receiver_{this};
+
+  mojo::Remote<crosapi::mojom::VideoFrameHandler> proxy_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_VIDEO_FRAME_HANDLER_ASH_H_
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 4571937b..cffa062 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -612,6 +612,8 @@
     "../ash/borealis/borealis_context_manager_impl.h",
     "../ash/borealis/borealis_features.cc",
     "../ash/borealis/borealis_features.h",
+    "../ash/borealis/borealis_game_mode_controller.cc",
+    "../ash/borealis/borealis_game_mode_controller.h",
     "../ash/borealis/borealis_installer.cc",
     "../ash/borealis/borealis_installer.h",
     "../ash/borealis/borealis_installer_impl.cc",
@@ -707,6 +709,12 @@
     "../ash/crosapi/test_mojo_connection_manager.h",
     "../ash/crosapi/url_handler_ash.cc",
     "../ash/crosapi/url_handler_ash.h",
+    "../ash/crosapi/video_capture_device_ash.cc",
+    "../ash/crosapi/video_capture_device_ash.h",
+    "../ash/crosapi/video_capture_device_factory_ash.cc",
+    "../ash/crosapi/video_capture_device_factory_ash.h",
+    "../ash/crosapi/video_frame_handler_ash.cc",
+    "../ash/crosapi/video_frame_handler_ash.h",
     "../ash/crosapi/window_util.cc",
     "../ash/crosapi/window_util.h",
     "../ash/login/app_mode/kiosk_launch_controller.cc",
@@ -3509,6 +3517,7 @@
     "../ash/borealis/borealis_context_manager_unittest.cc",
     "../ash/borealis/borealis_context_unittest.cc",
     "../ash/borealis/borealis_features_unittest.cc",
+    "../ash/borealis/borealis_game_mode_controller_unittest.cc",
     "../ash/borealis/borealis_installer_unittest.cc",
     "../ash/borealis/borealis_launch_watcher_unittest.cc",
     "../ash/borealis/borealis_shutdown_monitor_unittest.cc",
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc b/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
index 3314f99..0a10190 100644
--- a/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
@@ -410,7 +410,10 @@
   ASSERT_EQ(count + 1u, BrowserList::GetInstance()->size());
 
   // TODO(sammiequon): Check the values from the actual browser window.
-  auto stored_window_info = ::full_restore::GetWindowInfo(kId);
+  auto window = std::make_unique<aura::Window>(nullptr);
+  window->Init(ui::LAYER_NOT_DRAWN);
+  window->SetProperty(::full_restore::kRestoreWindowIdKey, kId);
+  auto stored_window_info = ::full_restore::GetWindowInfo(window.get());
   EXPECT_EQ(kDeskId, *stored_window_info->desk_id);
   EXPECT_EQ(kRestoreBounds, *stored_window_info->restore_bounds);
   EXPECT_EQ(kCurrentBounds, *stored_window_info->current_bounds);
@@ -474,7 +477,7 @@
 
   // Close the window.
   CloseAppWindow(app_window);
-  ASSERT_FALSE(::full_restore::GetWindowInfo(restore_window_id));
+  ASSERT_FALSE(::full_restore::GetWindowInfo(window));
 
   // Remove the added desks.
   while (true) {
diff --git a/chrome/browser/chromeos/guest_os/guest_os_external_protocol_handler.cc b/chrome/browser/chromeos/guest_os/guest_os_external_protocol_handler.cc
index 1b23c9da6..c768be5d 100644
--- a/chrome/browser/chromeos/guest_os/guest_os_external_protocol_handler.cc
+++ b/chrome/browser/chromeos/guest_os/guest_os_external_protocol_handler.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/chromeos/guest_os/guest_os_external_protocol_handler.h"
 
 #include "base/time/time.h"
+#include "chrome/browser/ash/borealis/borealis_app_launcher.h"
+#include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_files.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
@@ -66,6 +68,11 @@
                                    {url.spec()}, base::DoNothing());
       break;
 
+    case VmType::ApplicationList_VmType_BOREALIS:
+      borealis::BorealisService::GetForProfile(profile)->AppLauncher().Launch(
+          registration->app_id(), {url.spec()}, base::DoNothing());
+      break;
+
     default:
       LOG(ERROR) << "Unsupported VmType: "
                  << static_cast<int>(registration->VmType());
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index ca86ca1..12fd069 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -957,11 +957,17 @@
     sources += [
       "api/enterprise_device_attributes/enterprise_device_attributes_api.h",
       "api/enterprise_platform_keys/enterprise_platform_keys_api.h",
+      "api/messaging/native_message_built_in_host.cc",
+      "api/messaging/native_message_built_in_host.h",
+      "api/messaging/native_message_echo_host.cc",
+      "api/messaging/native_message_echo_host.h",
       "api/platform_keys/platform_keys_api.h",
     ]
     deps += [
       "//chromeos/crosapi/cpp",
       "//chromeos/crosapi/mojom",
+      "//remoting/host",
+      "//remoting/host/it2me:chrome_os_host",
     ]
     if (is_chromeos_lacros) {
       sources += [
@@ -969,11 +975,23 @@
         "api/enterprise_device_attributes/enterprise_device_attributes_api_lacros.h",
         "api/enterprise_platform_keys/enterprise_platform_keys_api_lacros.cc",
         "api/enterprise_platform_keys/enterprise_platform_keys_api_lacros.h",
+        "api/messaging/native_message_host_lacros.cc",
         "api/platform_keys/platform_keys_api_lacros.cc",
         "api/platform_keys/platform_keys_api_lacros.h",
       ]
       deps += [ "//chromeos/lacros" ]
+    } else {
+      sources += [ "api/messaging/native_message_host_chromeos.cc" ]
     }
+  } else {
+    sources += [
+      "api/messaging/native_message_process_host.cc",
+      "api/messaging/native_message_process_host.h",
+      "api/messaging/native_messaging_launch_from_native.cc",
+      "api/messaging/native_messaging_launch_from_native.h",
+      "api/messaging/native_process_launcher.cc",
+      "api/messaging/native_process_launcher.h",
+    ]
   }
 
   if (is_chromeos_ash) {
@@ -1006,7 +1024,6 @@
       "api/input_ime/input_ime_event_router_base.h",
       "api/media_perception_private/media_perception_api_delegate_chromeos.cc",
       "api/media_perception_private/media_perception_api_delegate_chromeos.h",
-      "api/messaging/native_message_host_chromeos.cc",
       "api/networking_cast_private/chrome_networking_cast_private_delegate.cc",
       "api/networking_cast_private/chrome_networking_cast_private_delegate.h",
       "api/networking_cast_private/networking_cast_private_api.cc",
@@ -1094,9 +1111,6 @@
       "//components/user_manager",
       "//media/capture:capture_lib",
       "//media/capture/video/chromeos/mojom:cros_camera",
-      "//remoting/base",
-      "//remoting/host",
-      "//remoting/host/it2me:chrome_os_host",
       "//third_party/protobuf:protobuf_lite",
       "//ui/base/ime/chromeos",
       "//ui/chromeos",
@@ -1125,12 +1139,6 @@
       "api/enterprise_reporting_private/keychain_data_helper_mac.h",
       "api/enterprise_reporting_private/keychain_data_helper_mac.mm",
       "api/image_writer_private/operation_nonchromeos.cc",
-      "api/messaging/native_message_process_host.cc",
-      "api/messaging/native_message_process_host.h",
-      "api/messaging/native_messaging_launch_from_native.cc",
-      "api/messaging/native_messaging_launch_from_native.h",
-      "api/messaging/native_process_launcher.cc",
-      "api/messaging/native_process_launcher.h",
       "api/tabs/tabs_util.cc",
       "chrome_kiosk_delegate.cc",
       "default_apps.cc",
diff --git a/chrome/browser/extensions/api/DEPS b/chrome/browser/extensions/api/DEPS
index 88f2d8a..e66c2600 100644
--- a/chrome/browser/extensions/api/DEPS
+++ b/chrome/browser/extensions/api/DEPS
@@ -3,7 +3,6 @@
   "+services/device/public",
 
    # Enable remote assistance on Chrome OS
-  "+remoting/base",
   "+remoting/host",
 ]
 
diff --git a/chrome/browser/extensions/api/messaging/native_message_built_in_host.cc b/chrome/browser/extensions/api/messaging/native_message_built_in_host.cc
new file mode 100644
index 0000000..97d7e38
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_message_built_in_host.cc
@@ -0,0 +1,55 @@
+// Copyright 2021 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/extensions/api/messaging/native_message_built_in_host.h"
+
+#include <string>
+
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/api/messaging/native_message_host.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/url_pattern.h"
+#include "ui/gfx/native_widget_types.h"
+#include "url/gurl.h"
+
+namespace extensions {
+
+namespace {
+
+bool MatchesSecurityOrigin(const NativeMessageBuiltInHost& host,
+                           const std::string& extension_id) {
+  GURL origin(std::string(kExtensionScheme) + "://" + extension_id);
+  for (size_t i = 0; i < host.allowed_origins_count; i++) {
+    URLPattern allowed_origin(URLPattern::SCHEME_ALL, host.allowed_origins[i]);
+    if (allowed_origin.MatchesSecurityOrigin(origin)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
+std::unique_ptr<NativeMessageHost> NativeMessageHost::Create(
+    content::BrowserContext* browser_context,
+    gfx::NativeView native_view,
+    const std::string& source_extension_id,
+    const std::string& native_host_name,
+    bool allow_user_level,
+    std::string* error) {
+  for (size_t i = 0; i < kBuiltInHostsCount; i++) {
+    const auto& host = kBuiltInHosts[i];
+    if (host.name == native_host_name) {
+      if (MatchesSecurityOrigin(host, source_extension_id)) {
+        return (*host.create_function)(browser_context);
+      }
+      *error = kForbiddenError;
+      return nullptr;
+    }
+  }
+  *error = kNotFoundError;
+  return nullptr;
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_message_built_in_host.h b/chrome/browser/extensions/api/messaging/native_message_built_in_host.h
new file mode 100644
index 0000000..71da8c6
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_message_built_in_host.h
@@ -0,0 +1,44 @@
+// Copyright 2021 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_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_BUILT_IN_HOST_H_
+#define CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_BUILT_IN_HOST_H_
+
+#include <memory>
+
+#include <stddef.h>
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class NativeMessageHost;
+
+struct NativeMessageBuiltInHost {
+  // Unique name to identify the built-in host.
+  const char* const name;
+
+  // The extension origins allowed to create the built-in host.
+  const char* const* const allowed_origins;
+
+  // The count of |allowed_origins|.
+  size_t allowed_origins_count;
+
+  // The factory function used to create new instances of this host.
+  std::unique_ptr<NativeMessageHost> (*create_function)(
+      content::BrowserContext*);
+};
+
+// The set of built-in hosts that can be instantiated. These are defined in the
+// platform-specific impl files.
+extern const NativeMessageBuiltInHost kBuiltInHosts[];
+
+// The count of built-in hosts defined in |kBuiltInHosts|.
+extern const size_t kBuiltInHostsCount;
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_BUILT_IN_HOST_H_
diff --git a/chrome/browser/extensions/api/messaging/native_message_echo_host.cc b/chrome/browser/extensions/api/messaging/native_message_echo_host.cc
new file mode 100644
index 0000000..93151b21
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_message_echo_host.cc
@@ -0,0 +1,75 @@
+// Copyright 2021 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/extensions/api/messaging/native_message_echo_host.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "content/public/browser/browser_context.h"
+
+namespace extensions {
+
+// static
+// Must match ScopedTestNativeMessagingHost::kHostName.
+const char* const NativeMessageEchoHost::kHostName =
+    "com.google.chrome.test.echo";
+
+// static
+// Must match ScopedTestNativeMessagingHost::kExtensionId.
+const char* const NativeMessageEchoHost::kOrigins[] = {
+    "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"};
+
+// static
+const int NativeMessageEchoHost::kOriginCount = base::size(kOrigins);
+
+// static
+std::unique_ptr<NativeMessageHost> NativeMessageEchoHost::Create(
+    content::BrowserContext* browser_context) {
+  return std::make_unique<NativeMessageEchoHost>();
+}
+
+NativeMessageEchoHost::NativeMessageEchoHost() = default;
+NativeMessageEchoHost::~NativeMessageEchoHost() = default;
+
+void NativeMessageEchoHost::Start(Client* client) {
+  client_ = client;
+}
+
+void NativeMessageEchoHost::OnMessage(const std::string& request_string) {
+  base::Optional<base::Value> request_value =
+      base::JSONReader::Read(request_string);
+  if (!request_value.has_value()) {
+    client_->CloseChannel(kHostInputOutputError);
+  } else if (request_string.find("stopHostTest") != std::string::npos) {
+    client_->CloseChannel(kNativeHostExited);
+  } else if (request_string.find("bigMessageTest") != std::string::npos) {
+    client_->CloseChannel(kHostInputOutputError);
+  } else {
+    ProcessEcho(base::Value::AsDictionaryValue(request_value.value()));
+  }
+}
+
+scoped_refptr<base::SingleThreadTaskRunner> NativeMessageEchoHost::task_runner()
+    const {
+  return base::ThreadTaskRunnerHandle::Get();
+}
+
+void NativeMessageEchoHost::ProcessEcho(const base::DictionaryValue& request) {
+  base::DictionaryValue response;
+  response.SetInteger("id", ++message_number_);
+  response.Set("echo", request.CreateDeepCopy());
+  response.SetString("caller_url", kOrigins[0]);
+  std::string response_string;
+  base::JSONWriter::Write(response, &response_string);
+  client_->PostMessageFromNativeHost(response_string);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_message_echo_host.h b/chrome/browser/extensions/api/messaging/native_message_echo_host.h
new file mode 100644
index 0000000..7c3e3ea2
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_message_echo_host.h
@@ -0,0 +1,60 @@
+// Copyright 2021 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_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_ECHO_HOST_H_
+#define CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_ECHO_HOST_H_
+
+#include <memory>
+#include <string>
+
+#include "extensions/browser/api/messaging/native_message_host.h"
+
+namespace base {
+class DictionaryValue;
+class SingleThreadTaskRunner;
+}  // namespace base
+
+namespace {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// A test NativeMessageHost used in ExtensionApiTest::NativeMessagingBasic.
+// See //chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+// The behavior in this implementation must match the expectations defined in
+// //chrome/test/data/native_messaging/native_hosts/echo.py as that script is
+// used to drive the tests.
+class NativeMessageEchoHost : public NativeMessageHost {
+ public:
+  static const char* const kHostName;
+  static const char* const kOrigins[];
+  static const int kOriginCount;
+
+  static std::unique_ptr<NativeMessageHost> Create(
+      content::BrowserContext* browser_context);
+
+  NativeMessageEchoHost();
+  NativeMessageEchoHost(const NativeMessageEchoHost&) = delete;
+  NativeMessageEchoHost& operator=(const NativeMessageEchoHost&) = delete;
+  ~NativeMessageEchoHost() override;
+
+  // NativeMessageHost implementation.
+  void Start(Client* client) override;
+  void OnMessage(const std::string& request_string) override;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override;
+
+ private:
+  void ProcessEcho(const base::DictionaryValue& request);
+
+  // Counter used to ensure message uniqueness for testing.
+  int message_number_ = 0;
+
+  // |client_| must outlive this test instance.
+  Client* client_ = nullptr;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGE_ECHO_HOST_H_
diff --git a/chrome/browser/extensions/api/messaging/native_message_host_chromeos.cc b/chrome/browser/extensions/api/messaging/native_message_host_chromeos.cc
index 0459cad..712e167 100644
--- a/chrome/browser/extensions/api/messaging/native_message_host_chromeos.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_host_chromeos.cc
@@ -8,93 +8,23 @@
 #include <string>
 #include <utility>
 
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/location.h"
-#include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/arc/extensions/arc_support_message_host.h"
 #include "chrome/browser/chromeos/drive/drivefs_native_message_host.h"
 #include "chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_messaging.h"
+#include "chrome/browser/extensions/api/messaging/native_message_built_in_host.h"
+#include "chrome/browser/extensions/api/messaging/native_message_echo_host.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "extensions/common/constants.h"
-#include "extensions/common/url_pattern.h"
+#include "remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h"
 #include "remoting/host/it2me/it2me_native_messaging_host_chromeos.h"
-#include "ui/gfx/native_widget_types.h"
-#include "url/gurl.h"
 
 namespace extensions {
 
 namespace {
 
-// A simple NativeMessageHost that mimics the implementation of
-// chrome/test/data/native_messaging/native_hosts/echo.py. It is currently
-// used for testing by ExtensionApiTest::NativeMessagingBasic.
-
-const char* const kEchoHostOrigins[] = {
-    // ScopedTestNativeMessagingHost::kExtensionId
-    "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"};
-
-class EchoHost : public NativeMessageHost {
- public:
-  static std::unique_ptr<NativeMessageHost> Create(
-      content::BrowserContext* browser_context) {
-    return std::make_unique<EchoHost>();
-  }
-
-  EchoHost() = default;
-
-  void Start(Client* client) override { client_ = client; }
-
-  void OnMessage(const std::string& request_string) override {
-    std::unique_ptr<base::Value> request_value =
-        base::JSONReader::ReadDeprecated(request_string);
-    std::unique_ptr<base::DictionaryValue> request(
-        static_cast<base::DictionaryValue*>(request_value.release()));
-    if (request_string.find("stopHostTest") != std::string::npos) {
-      client_->CloseChannel(kNativeHostExited);
-    } else if (request_string.find("bigMessageTest") != std::string::npos) {
-      client_->CloseChannel(kHostInputOutputError);
-    } else {
-      ProcessEcho(*request);
-    }
-  }
-
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override {
-    return base::ThreadTaskRunnerHandle::Get();
-  }
-
- private:
-  void ProcessEcho(const base::DictionaryValue& request) {
-    base::DictionaryValue response;
-    response.SetInteger("id", ++message_number_);
-    response.Set("echo", request.CreateDeepCopy());
-    response.SetString("caller_url", kEchoHostOrigins[0]);
-    std::string response_string;
-    base::JSONWriter::Write(response, &response_string);
-    client_->PostMessageFromNativeHost(response_string);
-  }
-
-  int message_number_ = 0;
-  Client* client_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(EchoHost);
-};
-
-struct BuiltInHost {
-  const char* const name;
-  const char* const* const allowed_origins;
-  int allowed_origins_count;
-  std::unique_ptr<NativeMessageHost> (*create_function)(
-      content::BrowserContext*);
-};
-
 std::unique_ptr<NativeMessageHost> CreateIt2MeHost(
     content::BrowserContext* browser_context) {
   return remoting::CreateIt2MeNativeMessagingHostForChromeOS(
@@ -102,67 +32,26 @@
       g_browser_process->policy_service());
 }
 
-// If you modify the list of allowed_origins, don't forget to update
-// remoting/host/it2me/com.google.chrome.remote_assistance.json.jinja2
-// to keep the two lists in sync.
-// TODO(kelvinp): Load the native messaging manifest as a resource file into
-// chrome and fetch the list of allowed_origins from the manifest (see
-// crbug/424743).
-const char* const kRemotingIt2MeOrigins[] = {
-    "chrome-extension://inomeogfingihgjfjlpeplalcfajhgai/",
-    "chrome-extension://hpodccmdligbeohchckkeajbfohibipg/"};
-
-bool MatchesSecurityOrigin(const BuiltInHost& host,
-                           const std::string& extension_id) {
-  GURL origin(std::string(kExtensionScheme) + "://" + extension_id);
-  for (int i = 0; i < host.allowed_origins_count; i++) {
-    URLPattern allowed_origin(URLPattern::SCHEME_ALL, host.allowed_origins[i]);
-    if (allowed_origin.MatchesSecurityOrigin(origin)) {
-      return true;
-    }
-  }
-  return false;
-}
-
 }  // namespace
 
-std::unique_ptr<NativeMessageHost> NativeMessageHost::Create(
-    content::BrowserContext* browser_context,
-    gfx::NativeView native_view,
-    const std::string& source_extension_id,
-    const std::string& native_host_name,
-    bool allow_user_level,
-    std::string* error) {
-  static const BuiltInHost kBuiltInHosts[] = {
-      // ScopedTestNativeMessagingHost::kHostName
-      {"com.google.chrome.test.echo", kEchoHostOrigins,
-       base::size(kEchoHostOrigins), &EchoHost::Create},
-      {"com.google.chrome.remote_assistance", kRemotingIt2MeOrigins,
-       base::size(kRemotingIt2MeOrigins), &CreateIt2MeHost},
-      {arc::ArcSupportMessageHost::kHostName,
-       arc::ArcSupportMessageHost::kHostOrigin, 1,
-       &arc::ArcSupportMessageHost::Create},
-      {chromeos::kWilcoDtcSupportdUiMessageHost,
-       chromeos::kWilcoDtcSupportdHostOrigins,
-       chromeos::kWilcoDtcSupportdHostOriginsSize,
-       &chromeos::CreateExtensionOwnedWilcoDtcSupportdMessageHost},
-      {drive::kDriveFsNativeMessageHostName,
-       drive::kDriveFsNativeMessageHostOrigins,
-       drive::kDriveFsNativeMessageHostOriginsSize,
-       &drive::CreateDriveFsNativeMessageHost},
-  };
+const NativeMessageBuiltInHost kBuiltInHosts[] = {
+    {NativeMessageEchoHost::kHostName, NativeMessageEchoHost::kOrigins,
+     NativeMessageEchoHost::kOriginCount, &NativeMessageEchoHost::Create},
+    {remoting::kIt2MeNativeMessageHostName, remoting::kIt2MeOrigins,
+     remoting::kIt2MeOriginsSize, &CreateIt2MeHost},
+    {arc::ArcSupportMessageHost::kHostName,
+     arc::ArcSupportMessageHost::kHostOrigin, 1,
+     &arc::ArcSupportMessageHost::Create},
+    {chromeos::kWilcoDtcSupportdUiMessageHost,
+     chromeos::kWilcoDtcSupportdHostOrigins,
+     chromeos::kWilcoDtcSupportdHostOriginsSize,
+     &chromeos::CreateExtensionOwnedWilcoDtcSupportdMessageHost},
+    {drive::kDriveFsNativeMessageHostName,
+     drive::kDriveFsNativeMessageHostOrigins,
+     drive::kDriveFsNativeMessageHostOriginsSize,
+     &drive::CreateDriveFsNativeMessageHost},
+};
 
-  for (const BuiltInHost& host : kBuiltInHosts) {
-    if (host.name == native_host_name) {
-      if (MatchesSecurityOrigin(host, source_extension_id)) {
-        return (*host.create_function)(browser_context);
-      }
-      *error = kForbiddenError;
-      return nullptr;
-    }
-  }
-  *error = kNotFoundError;
-  return nullptr;
-}
+const size_t kBuiltInHostsCount = base::size(kBuiltInHosts);
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_message_host_lacros.cc b/chrome/browser/extensions/api/messaging/native_message_host_lacros.cc
new file mode 100644
index 0000000..a2b5097
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_message_host_lacros.cc
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/messaging/native_message_host.h"
+
+#include <memory>
+#include <string>
+
+#include "base/stl_util.h"
+#include "chrome/browser/extensions/api/messaging/native_message_built_in_host.h"
+#include "chrome/browser/extensions/api/messaging/native_message_echo_host.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h"
+#include "remoting/host/it2me/it2me_native_messaging_host_lacros.h"
+
+namespace extensions {
+
+namespace {
+
+std::unique_ptr<NativeMessageHost> CreateIt2MeHost(
+    content::BrowserContext* browser_context) {
+  return remoting::CreateIt2MeNativeMessagingHostForLacros(
+      content::GetIOThreadTaskRunner({}), content::GetUIThreadTaskRunner({}));
+}
+
+}  // namespace
+
+const NativeMessageBuiltInHost kBuiltInHosts[] = {
+    {NativeMessageEchoHost::kHostName, NativeMessageEchoHost::kOrigins,
+     NativeMessageEchoHost::kOriginCount, &NativeMessageEchoHost::Create},
+    {remoting::kIt2MeNativeMessageHostName, remoting::kIt2MeOrigins,
+     remoting::kIt2MeOriginsSize, &CreateIt2MeHost},
+};
+
+const size_t kBuiltInHostsCount = base::size(kBuiltInHosts);
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
index d40535f..8134e92 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
@@ -83,7 +83,7 @@
   ASSERT_TRUE(RunTest("native_messaging_connect")) << message_;
 }
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
 
 class TestProcessManagerObserver : public ProcessManagerObserver {
  public:
@@ -437,7 +437,7 @@
   ASSERT_NO_FATAL_FAILURE(TestKeepAliveStateObserver().WaitForNoKeepAlive());
 }
 
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
 
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 0436690..d69d4cb 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -314,6 +314,10 @@
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[language::prefs::kFluentLanguages] =
       settings_api::PrefType::PREF_TYPE_LIST;
+  (*s_allowlist)[language::prefs::kSelectedLanguages] =
+      settings_api::PrefType::PREF_TYPE_STRING;
+  (*s_allowlist)[language::prefs::kForcedLanguages] =
+      settings_api::PrefType::PREF_TYPE_LIST;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   (*s_allowlist)[::prefs::kLanguageImeMenuActivated] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
diff --git a/chrome/browser/extensions/api/test/apitest_apitest.cc b/chrome/browser/extensions/api/test/apitest_apitest.cc
index f2451b1ba..a1ca08c 100644
--- a/chrome/browser/extensions/api/test/apitest_apitest.cc
+++ b/chrome/browser/extensions/api/test/apitest_apitest.cc
@@ -4,6 +4,7 @@
 
 #include <memory>
 
+#include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/test/result_catcher.h"
@@ -13,21 +14,18 @@
 
 namespace {
 
-constexpr char kBackgroundScriptManifestStub[] =
+constexpr char kManifestStub[] =
     R"({
          "name": "extension",
          "version": "0.1",
-         "manifest_version": 2,
-         "background": { "scripts": ["background.js"] }
+         "manifest_version": %d,
+         "background": { %s }
        })";
 
-constexpr char kWorkerScriptManifestStub[] =
-    R"({
-         "name": "extension",
-         "version": "0.1",
-         "manifest_version": 3,
-         "background": { "service_worker": "worker.js" }
-       })";
+constexpr char kPersistentBackground[] = R"("scripts": ["background.js"])";
+
+constexpr char kServiceWorkerBackground[] =
+    R"("service_worker": "background.js")";
 
 // NOTE(devlin): When running tests using the chrome.tests.runTests API, it's
 // not possible to validate the failure message of individual sub-tests using
@@ -42,51 +40,59 @@
 
 }  // namespace
 
+using ContextType = ExtensionApiTest::ContextType;
+
 class TestAPITest : public ExtensionApiTest {
- public:
-  ~TestAPITest() override = default;
+ protected:
+  const Extension* LoadExtensionScriptWithContext(const char* background_script,
+                                                  ContextType context_type,
+                                                  int manifest_version);
 
-  // Loads and returns an extension with the given |background_script| as a
-  // manifest v2 extension.
-  const Extension* LoadExtensionWithBackgroundScript(
-      const char* background_script);
-
-  // Loads and returns an extension with the given |worker_script| as a manifest
-  // v3 extension.
-  const Extension* LoadExtensionWithWorkerScript(const char* worker_script);
-
- private:
   std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_;
 };
 
-const Extension* TestAPITest::LoadExtensionWithBackgroundScript(
-    const char* background_script) {
+const Extension* TestAPITest::LoadExtensionScriptWithContext(
+    const char* background_script,
+    ContextType context_type,
+    int manifest_version = 2) {
   auto test_dir = std::make_unique<TestExtensionDir>();
-  test_dir->WriteManifest(kBackgroundScriptManifestStub);
+  const char* background_value = context_type == ContextType::kServiceWorker
+                                     ? kServiceWorkerBackground
+                                     : kPersistentBackground;
+  const std::string manifest =
+      base::StringPrintf(kManifestStub, manifest_version, background_value);
+  test_dir->WriteManifest(manifest);
   test_dir->WriteFile(FILE_PATH_LITERAL("background.js"), background_script);
   const Extension* extension = LoadExtension(test_dir->UnpackedPath());
   test_dirs_.push_back(std::move(test_dir));
   return extension;
 }
 
-const Extension* TestAPITest::LoadExtensionWithWorkerScript(
-    const char* worker_script) {
-  auto test_dir = std::make_unique<TestExtensionDir>();
-  test_dir->WriteManifest(kWorkerScriptManifestStub);
-  test_dir->WriteFile(FILE_PATH_LITERAL("worker.js"), worker_script);
-  const Extension* extension = LoadExtension(test_dir->UnpackedPath());
-  test_dirs_.push_back(std::move(test_dir));
-  return extension;
-}
+class TestAPITestWithContextType
+    : public TestAPITest,
+      public testing::WithParamInterface<ContextType> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    PersistentBackground,
+    TestAPITestWithContextType,
+    ::testing::Values(ExtensionApiTest::ContextType::kPersistentBackground));
+
+INSTANTIATE_TEST_SUITE_P(
+    ServiceWorker,
+    TestAPITestWithContextType,
+    ::testing::Values(ExtensionApiTest::ContextType::kServiceWorker));
 
 // TODO(devlin): This test name should be more descriptive.
-IN_PROC_BROWSER_TEST_F(TestAPITest, ApiTest) {
-  ASSERT_TRUE(RunExtensionTest("apitest")) << message_;
+IN_PROC_BROWSER_TEST_P(TestAPITestWithContextType, ApiTest) {
+  ASSERT_TRUE(RunExtensionTest(
+      {.name = "apitest"},
+      {.load_as_service_worker = GetParam() == ContextType::kServiceWorker}))
+      << message_;
 }
 
 // Verifies that failing an assert in a promise will properly fail and end the
 // test.
-IN_PROC_BROWSER_TEST_F(TestAPITest, FailedAssertsInPromises) {
+IN_PROC_BROWSER_TEST_P(TestAPITestWithContextType, FailedAssertsInPromises) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -98,13 +104,14 @@
              p.then(() => { chrome.test.succeed(); });
            }
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithBackgroundScript(kBackgroundJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kBackgroundJs, GetParam()));
   EXPECT_FALSE(result_catcher.GetNextResult());
   EXPECT_EQ(kExpectedFailureMessage, result_catcher.message());
 }
 
 // Verifies that using await and assert'ing aspects of the results succeeds.
-IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_Succeed) {
+IN_PROC_BROWSER_TEST_P(TestAPITestWithContextType,
+                       AsyncAwaitAssertions_Succeed) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -116,13 +123,14 @@
              chrome.test.succeed();
            }
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithBackgroundScript(kBackgroundJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kBackgroundJs, GetParam()));
   EXPECT_TRUE(result_catcher.GetNextResult());
 }
 
 // Verifies that using await and having failed assertions properly fails the
 // test.
-IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_Failed) {
+IN_PROC_BROWSER_TEST_P(TestAPITestWithContextType,
+                       AsyncAwaitAssertions_Failed) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -134,31 +142,11 @@
              chrome.test.succeed();
            }
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithBackgroundScript(kBackgroundJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kBackgroundJs, GetParam()));
   EXPECT_FALSE(result_catcher.GetNextResult());
   EXPECT_EQ(kExpectedFailureMessage, result_catcher.message());
 }
 
-// Verifies that we can assert values on chrome.runtime.lastError after using
-// await with an API call.
-IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_LastError) {
-  ResultCatcher result_catcher;
-  constexpr char kBackgroundJs[] =
-      R"(chrome.test.runTests([
-           async function asyncAssertions() {
-             let result = await new Promise((resolve) => {
-               const nonexistentId = 99999;
-               chrome.tabs.update(
-                   nonexistentId, {url: 'https://example.com'}, resolve);
-             });
-             chrome.test.assertLastError('No tab with id: 99999.');
-             chrome.test.succeed();
-           }
-         ]);)";
-  ASSERT_TRUE(LoadExtensionWithBackgroundScript(kBackgroundJs));
-  EXPECT_TRUE(result_catcher.GetNextResult());
-}
-
 // Verifies that chrome.test.assertPromiseRejects() succeeds using
 // promises that reject with the expected message.
 IN_PROC_BROWSER_TEST_F(TestAPITest, AssertPromiseRejects_Successful) {
@@ -192,7 +180,9 @@
              chrome.test.succeed();
            },
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithWorkerScript(kWorkerJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kWorkerJs,
+                                             ContextType::kServiceWorker,
+                                             /*manifest_version=*/3));
   EXPECT_TRUE(result_catcher.GetNextResult());
 }
 
@@ -208,7 +198,9 @@
              chrome.test.succeed();
            },
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithWorkerScript(kWorkerJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kWorkerJs,
+                                             ContextType::kServiceWorker,
+                                             /*manifest_version=*/3));
   EXPECT_FALSE(result_catcher.GetNextResult());
   EXPECT_EQ(kExpectedFailureMessage, result_catcher.message());
 }
@@ -225,7 +217,9 @@
              chrome.test.succeed();
            },
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithWorkerScript(kWorkerJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kWorkerJs,
+                                             ContextType::kServiceWorker,
+                                             /*manifest_version=*/3));
   EXPECT_FALSE(result_catcher.GetNextResult());
   EXPECT_EQ(kExpectedFailureMessage, result_catcher.message());
 }
@@ -242,7 +236,9 @@
              chrome.test.succeed();
            },
          ]);)";
-  ASSERT_TRUE(LoadExtensionWithWorkerScript(kWorkerJs));
+  ASSERT_TRUE(LoadExtensionScriptWithContext(kWorkerJs,
+                                             ContextType::kServiceWorker,
+                                             /*manifest_version=*/3));
   EXPECT_FALSE(result_catcher.GetNextResult());
   EXPECT_EQ(kExpectedFailureMessage, result_catcher.message());
 }
diff --git a/chrome/browser/feed/android/BUILD.gn b/chrome/browser/feed/android/BUILD.gn
index 1ed76ec..f45226a7 100644
--- a/chrome/browser/feed/android/BUILD.gn
+++ b/chrome/browser/feed/android/BUILD.gn
@@ -11,6 +11,7 @@
   sources = [
     "java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedBridge.java",
     "java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroController.java",
+    "java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroView.java",
     "java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java",
   ]
   deps = [
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroController.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroController.java
index 8fa5111..ef628113 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroController.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroController.java
@@ -5,8 +5,7 @@
 package org.chromium.chrome.browser.feed.webfeed;
 
 import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Handler;
+import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -14,10 +13,7 @@
 import org.chromium.chrome.browser.tab.CurrentTabObserver;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
-import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
-import org.chromium.components.browser_ui.widget.textbubble.ClickableTextBubble;
-import org.chromium.ui.widget.ViewRectProvider;
+import org.chromium.components.browser_ui.widget.LoadingView;
 import org.chromium.url.GURL;
 
 /**
@@ -25,10 +21,10 @@
  */
 public class WebFeedFollowIntroController {
     private final Activity mActivity;
-    private final Handler mHandler = new Handler();
-    private final View mMenuButtonAnchorView;
+    private final CurrentTabObserver mCurrentTabObserver;
+    private final WebFeedFollowIntroView mWebFeedFollowIntroView;
 
-    private CurrentTabObserver mCurrentTabObserver;
+    private boolean mAcceleratorPressed;
 
     /**
      * Constructs an instance of {@link WebFeedFollowIntroController}.
@@ -40,14 +36,19 @@
     public WebFeedFollowIntroController(
             Activity activity, ObservableSupplier<Tab> tabSupplier, View menuButtonAnchorView) {
         mActivity = activity;
-        mMenuButtonAnchorView = menuButtonAnchorView;
+        mWebFeedFollowIntroView = new WebFeedFollowIntroView(mActivity, menuButtonAnchorView);
 
         mCurrentTabObserver = new CurrentTabObserver(tabSupplier, new EmptyTabObserver() {
             @Override
             public void onPageLoadFinished(Tab tab, GURL url) {
-                // TODO(sophey): Add proper heuristics for showing the accelerator. Also add IPH
-                // variation.
-                maybeShowFollowAccelerator();
+                // TODO(sophey): Add proper heuristics for showing the accelerator.
+                mAcceleratorPressed = false;
+                maybeShowFollowIntro(url);
+            }
+
+            @Override
+            public void onPageLoadStarted(Tab tab, GURL url) {
+                mWebFeedFollowIntroView.dismissBubble();
             }
         }, /*swapCallback=*/null);
     }
@@ -56,46 +57,54 @@
         mCurrentTabObserver.destroy();
     }
 
-    private void maybeShowFollowAccelerator() {
-        if (!shouldShowFollowAccelerator()) {
+    private void maybeShowFollowIntro(GURL url) {
+        if (!shouldShowFollowIntro()) {
             return;
         }
-
-        ViewRectProvider rectProvider = new ViewRectProvider(mMenuButtonAnchorView);
-        int yInsetPx = mActivity.getResources().getDimensionPixelOffset(
-                R.dimen.iph_text_bubble_menu_anchor_y_inset);
-        Rect insetRect = new Rect(0, 0, 0, yInsetPx);
-        rectProvider.setInsetPx(insetRect);
-
-        View.OnTouchListener onTouchListener = new View.OnTouchListener() {
-            @Override
-            public boolean onTouch(View view, MotionEvent motionEvent) {
-                // TODO(sophey): Hook up follow functionality and implement post-follow animation.
-                view.performClick();
-                return true;
-            }
-        };
-
-        ClickableTextBubble textBubble = new ClickableTextBubble(mActivity, mMenuButtonAnchorView,
-                R.string.menu_follow, R.string.menu_follow, rectProvider, R.drawable.ic_add,
-                ChromeAccessibilityUtil.get().isAccessibilityEnabled(), onTouchListener);
-        // TODO(sophey): Remove once onClick functionality is implemented.
-        textBubble.setDismissOnTouchInteraction(true);
-        textBubble.addOnDismissListener(() -> mHandler.postDelayed(() -> {
-            turnOffHighlightForFollowMenuItem();
-        }, ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS));
-        turnOnHighlightForFollowMenuItem();
-
-        textBubble.show();
+        // TODO(crbug/1152592): Add IPH variation.
+        showFollowAccelerator(url);
     }
 
-    private boolean shouldShowFollowAccelerator() {
+    private void showFollowAccelerator(GURL url) {
+        GestureDetector gestureDetector = new GestureDetector(
+                mActivity.getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
+                    @Override
+                    public boolean onSingleTapUp(MotionEvent motionEvent) {
+                        if (!mAcceleratorPressed) {
+                            mAcceleratorPressed = true;
+                            performFollowWithAccelerator(url);
+                        }
+                        return true;
+                    }
+                });
+        View.OnTouchListener onTouchListener = (view, motionEvent) -> {
+            view.performClick();
+            gestureDetector.onTouchEvent(motionEvent);
+            return true;
+        };
+        mWebFeedFollowIntroView.showAccelerator(onTouchListener);
+    }
+
+    private void performFollowWithAccelerator(GURL url) {
+        mWebFeedFollowIntroView.showLoadingUI();
+        WebFeedBridge bridge = new WebFeedBridge();
+        bridge.followFromUrl(
+                url, result -> mWebFeedFollowIntroView.hideLoadingUI(new LoadingView.Observer() {
+                    @Override
+                    public void onShowLoadingUIComplete() {}
+
+                    @Override
+                    public void onHideLoadingUIComplete() {
+                        mWebFeedFollowIntroView.dismissBubble();
+                        if (result.success) {
+                            mWebFeedFollowIntroView.showFollowingBubble();
+                        }
+                        // TODO(crbug/1152592): Add snackbar for failure.
+                    }
+                }));
+    }
+
+    private boolean shouldShowFollowIntro() {
         return true;
     }
-
-    private void turnOnHighlightForFollowMenuItem() {
-        // TODO(crbug/1152592): Figure out how to highlight footer.
-    }
-
-    private void turnOffHighlightForFollowMenuItem() {}
 }
\ No newline at end of file
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroView.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroView.java
new file mode 100644
index 0000000..6b395bf
--- /dev/null
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedFollowIntroView.java
@@ -0,0 +1,101 @@
+// Copyright 2021 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.feed.webfeed;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.View;
+
+import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
+import org.chromium.components.browser_ui.widget.LoadingView;
+import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
+import org.chromium.components.browser_ui.widget.textbubble.ClickableTextBubble;
+import org.chromium.components.browser_ui.widget.textbubble.TextBubble;
+import org.chromium.ui.widget.ViewRectProvider;
+
+/**
+ * Manages the view of the WebFeed follow intro.
+ */
+class WebFeedFollowIntroView {
+    private static final int sAcceleratorTimeout = 10 * 1000; // 10 seconds
+
+    private final Activity mActivity;
+    private final Handler mHandler = new Handler();
+    private final View mMenuButtonAnchorView;
+
+    private ClickableTextBubble mFollowBubble;
+
+    /**
+     * Constructs an instance of {@link WebFeedFollowIntroView}.
+     *
+     * @param activity The current {@link Activity}.
+     * @param menuButtonAnchorView The menu button {@link View} to serve as an anchor.
+     */
+    WebFeedFollowIntroView(Activity activity, View menuButtonAnchorView) {
+        mActivity = activity;
+        mMenuButtonAnchorView = menuButtonAnchorView;
+    }
+
+    void showAccelerator(View.OnTouchListener onTouchListener) {
+        mFollowBubble = new ClickableTextBubble(mActivity, mMenuButtonAnchorView,
+                R.string.menu_follow, R.string.menu_follow, createRectProvider(), R.drawable.ic_add,
+                ChromeAccessibilityUtil.get().isAccessibilityEnabled(), onTouchListener);
+        mFollowBubble.addOnDismissListener(
+                ()
+                        -> mHandler.postDelayed(this::turnOffHighlightForFollowMenuItem,
+                                ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS));
+        // TODO(crbug/1152592): Figure out a way to dismiss on outside taps as well.
+        mFollowBubble.setAutoDismissTimeout(sAcceleratorTimeout);
+        turnOnHighlightForFollowMenuItem();
+
+        mFollowBubble.show();
+    }
+
+    void showLoadingUI() {
+        if (mFollowBubble != null) {
+            mFollowBubble.showLoadingUI(R.string.web_feed_follow_loading_description);
+        }
+    }
+
+    void hideLoadingUI(LoadingView.Observer loadingViewObserver) {
+        if (mFollowBubble != null) {
+            mFollowBubble.hideLoadingUI(loadingViewObserver);
+        }
+    }
+
+    void dismissBubble() {
+        if (mFollowBubble != null) {
+            mFollowBubble.dismiss();
+            mFollowBubble.destroy();
+            mFollowBubble = null;
+        }
+    }
+
+    void showFollowingBubble() {
+        TextBubble followingBubble = new TextBubble(mActivity, mMenuButtonAnchorView,
+                R.string.menu_following, R.string.menu_following, /*showArrow=*/false,
+                createRectProvider(), R.drawable.ic_done_blue, /*isRoundBubble=*/true,
+                /*inverseColor=*/true, ChromeAccessibilityUtil.get().isAccessibilityEnabled());
+        followingBubble.setDismissOnTouchInteraction(true);
+        followingBubble.show();
+    }
+
+    private ViewRectProvider createRectProvider() {
+        ViewRectProvider rectProvider = new ViewRectProvider(mMenuButtonAnchorView);
+        int yInsetPx = mActivity.getResources().getDimensionPixelOffset(
+                R.dimen.iph_text_bubble_menu_anchor_y_inset);
+        Rect insetRect = new Rect(0, 0, 0, yInsetPx);
+        rectProvider.setInsetPx(insetRect);
+
+        return rectProvider;
+    }
+
+    private void turnOnHighlightForFollowMenuItem() {
+        // TODO(crbug/1152592): Figure out how to highlight footer.
+    }
+
+    private void turnOffHighlightForFollowMenuItem() {}
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index dfc63ca..30ec624 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4039,10 +4039,6 @@
     "owners": [ "cros-customization@google.com", "hsuregan", "khorimoto" ],
     "expiry_milestone": 90
   },
-  { "name": "os-settings-polymer3",
-    "owners": [ "cros-customization@google.com", "tjohnsonkanu", "khorimoto" ],
-    "expiry_milestone": 90
-  },
   {
     "name": "overlay-scrollbars",
     "owners": [ "chaopeng", "bokan", "input-dev" ],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 762936c9..644ae74c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4657,10 +4657,6 @@
 const char kNearbySharingWebRtcDescription[] =
     "Enables use of WebRTC in Nearby Share.";
 
-const char kOsSettingsPolymer3Name[] = "Enable OS Settings Polymer3";
-const char kOsSettingsPolymer3Description[] =
-    "Flips chrome://os-settings to show Polymer3 version.";
-
 const char kPhoneHubName[] = "Enable Phone Hub";
 const char kPhoneHubDescription[] =
     "Provides a UI for users to view information about their Android phone "
@@ -4846,10 +4842,6 @@
     "Enable unified media view to browse recently-modified media files from"
     " local disk, Google Drive, and Android.";
 
-const char kVaapiAV1DecoderName[] = "VA-API decode acceleration for AV1";
-const char kVaapiAV1DecoderDescription[] =
-    "Enable or disable decode acceleration of AV1 videos using the VA-API.";
-
 const char kVaapiJpegImageDecodeAccelerationName[] =
     "VA-API JPEG decode acceleration for images";
 const char kVaapiJpegImageDecodeAccelerationDescription[] =
@@ -4904,6 +4896,10 @@
 const char kDeprecateLowUsageCodecsDescription[] =
     "Deprecates low usage codecs. Disable this feature to allow playback of "
     "AMR and GSM.";
+
+const char kVaapiAV1DecoderName[] = "VA-API decode acceleration for AV1";
+const char kVaapiAV1DecoderDescription[] =
+    "Enable or disable decode acceleration of AV1 videos using the VA-API.";
 #endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_CHROMEOS) && BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index e59a205..2ad4087c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2736,9 +2736,6 @@
 extern const char kNearbySharingWebRtcName[];
 extern const char kNearbySharingWebRtcDescription[];
 
-extern const char kOsSettingsPolymer3Name[];
-extern const char kOsSettingsPolymer3Description[];
-
 extern const char kPhoneHubName[];
 extern const char kPhoneHubDescription[];
 
@@ -2844,10 +2841,6 @@
 extern const char kUseFakeDeviceForMediaStreamName[];
 extern const char kUseFakeDeviceForMediaStreamDescription[];
 
-// TODO(b/177462291): make flag available on LaCrOS.
-extern const char kVaapiAV1DecoderName[];
-extern const char kVaapiAV1DecoderDescription[];
-
 extern const char kVaapiJpegImageDecodeAccelerationName[];
 extern const char kVaapiJpegImageDecodeAccelerationDescription[];
 
@@ -2882,6 +2875,9 @@
 #if defined(OS_CHROMEOS)
 extern const char kDeprecateLowUsageCodecsName[];
 extern const char kDeprecateLowUsageCodecsDescription[];
+
+extern const char kVaapiAV1DecoderName[];
+extern const char kVaapiAV1DecoderDescription[];
 #endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_CHROMEOS) && BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
index 367c650..f951786 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
@@ -162,6 +162,16 @@
   }
 }
 
+bool NearbyShareAction::OnAcceleratorPressed(
+    const ui::Accelerator& accelerator) {
+  // This is overridden because the default case returns false
+  // which means the accelerator has not been handled by the ShareAction. In
+  // that case, the sharesheet handles it by closing the UI. We return true
+  // instead to indicate we will handle the accelerator ourselves, which
+  // prevents the UI from being closed by the sharesheet.
+  return true;
+}
+
 bool NearbyShareAction::HandleKeyboardEvent(
     content::WebContents* source,
     const content::NativeWebKeyboardEvent& event) {
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
index 99ed1c9..9368e4d 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
@@ -32,6 +32,7 @@
   void OnClosing(sharesheet::SharesheetController* controller) override;
   bool ShouldShowAction(const apps::mojom::IntentPtr& intent,
                         bool contains_hosted_document) override;
+  bool OnAcceleratorPressed(const ui::Accelerator& accelerator) override;
 
   // nearby_share::NearbyShareDialogUI::Observer:
   void OnClose() override;
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 8be5408..5182529 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -629,6 +629,9 @@
   { key::kBrowserLabsEnabled,
     chrome_labs_prefs::kBrowserLabsEnabled,
     base::Value::Type::BOOLEAN },
+  { key::kForcedLanguages,
+    language::prefs::kForcedLanguages,
+    base::Value::Type::LIST },
 
 #if defined(OS_ANDROID)
   { key::kDataCompressionProxyEnabled,
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/SharedPreferencesManager.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/SharedPreferencesManager.java
index ae5a68e2..c17f4df3 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/SharedPreferencesManager.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/SharedPreferencesManager.java
@@ -53,7 +53,7 @@
 
     private void maybeInitializeChecker() {
         // Create a working key checker, which does not happen in production builds.
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             mKeyChecker = ChromePreferenceKeyChecker.getInstance();
         }
     }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index c97e91c..587a6f2 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -120,7 +120,7 @@
   "background/es6_loader.js",
   "background/find_handler.js",
   "background/media_automation_handler.js",
-  "background/next_earcons.js",
+  "background/earcons.js",
   "background/range_automation_handler.js",
 ]
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index ee30d41..f6338ee 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {Earcons} from './earcons.js';
 import {FindHandler} from './find_handler.js';
 import {LiveRegions} from './live_regions.js';
 import {MediaAutomationHandler} from './media_automation_handler.js';
-import {NextEarcons} from './next_earcons.js';
 import {RangeAutomationHandler} from './range_automation_handler.js';
 
 /**
@@ -37,12 +37,12 @@
     this.currentRange_ = null;
 
     /** @type {!AbstractEarcons} @private */
-    this.nextEarcons_ = new NextEarcons();
+    this.earcons_ = new Earcons();
 
     // Read-only earcons.
     Object.defineProperty(ChromeVox, 'earcons', {
       get: (function() {
-             return this.nextEarcons_;
+             return this.earcons_;
            }).bind(this)
     });
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 3e710ec..d6b6efe 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -18,6 +18,7 @@
     window.simulateHitTestResult = this.simulateHitTestResult;
     window.press = this.press;
     window.Mod = constants.ModifierFlag;
+    window.ActionType = chrome.automation.ActionType;
 
     this.forceContextualLastOutput();
   }
@@ -2062,6 +2063,7 @@
               assertEquals(RoleType.BUTTON, evt.target.role);
               assertEquals('action', evt.eventFrom);
               assertEquals('cancel', evt.target.name);
+              assertEquals('focus', evt.eventFromAction);
             }));
 
         button.focus();
@@ -2669,12 +2671,14 @@
           }
         });
 
-        const evt2 = new CustomAutomationEvent(EventType.FOCUS, group2, '', []);
+        const evt2 =
+            new CustomAutomationEvent(EventType.FOCUS, group2, '', '', []);
         const currentRange = ChromeVoxState.instance.currentRange;
         DesktopAutomationHandler.instance.onFocus(evt2);
         assertEquals(currentRange, ChromeVoxState.instance.currentRange);
 
-        const evt1 = new CustomAutomationEvent(EventType.FOCUS, group1, '', []);
+        const evt1 =
+            new CustomAutomationEvent(EventType.FOCUS, group1, '', '', []);
         mockFeedback
             .call(DesktopAutomationHandler.instance.onFocus.bind(
                 DesktopAutomationHandler.instance, evt1))
@@ -2902,7 +2906,7 @@
         }());
         const button = root.find({role: RoleType.BUTTON});
         const alertEvt =
-            new CustomAutomationEvent(EventType.ALERT, button, '', []);
+            new CustomAutomationEvent(EventType.ALERT, button, '', '', []);
         mockFeedback
             .call(DesktopAutomationHandler.instance.onAlert.bind(
                 DesktopAutomationHandler.instance, alertEvt))
@@ -2926,7 +2930,7 @@
 
         const button = root.find({role: RoleType.BUTTON});
         const alertEvt =
-            new CustomAutomationEvent(EventType.ALERT, button, '', []);
+            new CustomAutomationEvent(EventType.ALERT, button, '', '', []);
         mockFeedback
             .call(DesktopAutomationHandler.instance.onAlert.bind(
                 DesktopAutomationHandler.instance, alertEvt))
@@ -3354,3 +3358,31 @@
         .replay();
   });
 });
+
+TEST_F('ChromeVoxBackgroundTest', 'FocusAfterClick', function() {
+  const mockFeedback = this.createMockFeedback();
+  const site = `
+    <p>Start</p>
+    <button id="clickMe">Click me</button>
+    <h1 id="focusMe" tabindex=-1>Focus me</h1>
+    <script>
+      document.getElementById('clickMe').addEventListener('click', function() {
+        document.getElementById('focusMe').focus();
+      }, false);
+    </script>
+  `;
+  this.runWithLoadedTree(site, function(root) {
+    DesktopAutomationHandler.announceActions = false;
+    mockFeedback.expectSpeech('Start')
+        .call(doCmd('nextObject'))
+        .expectSpeech('Click me')
+        .call(doCmd('forceClickOnCurrentItem'))
+        .expectSpeech('Focus me')
+        .call(() => {
+          assertEquals(
+              'Focus me',
+              ChromeVoxState.instance.getCurrentRange().start.node.name);
+        })
+        .replay();
+  });
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
index 496d90b..4171ca2 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/base_automation_handler.js
@@ -10,6 +10,7 @@
 goog.provide('BaseAutomationHandler');
 
 goog.scope(function() {
+const ActionType = chrome.automation.ActionType;
 const AutomationEvent = chrome.automation.AutomationEvent;
 const AutomationNode = chrome.automation.AutomationNode;
 const EventType = chrome.automation.EventType;
@@ -100,7 +101,8 @@
 
     // Decide whether to announce and sync this event.
     if (!DesktopAutomationHandler.announceActions &&
-        evt.eventFrom === 'action') {
+        evt.eventFrom === 'action' &&
+        evt.eventFromAction !== ActionType.DO_DEFAULT) {
       return;
     }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index cd076deb..6506d77 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -24,6 +24,7 @@
 goog.require('CommandStore');
 
 goog.scope(function() {
+const ActionType = chrome.automation.ActionType;
 const AutomationEvent = chrome.automation.AutomationEvent;
 const AutomationNode = chrome.automation.AutomationNode;
 const Dir = constants.Dir;
@@ -1336,7 +1337,7 @@
   CommandHandler.imageNode_ = imageNode;
   if (imageNode.imageDataUrl) {
     const event = new CustomAutomationEvent(
-        EventType.IMAGE_FRAME_UPDATED, imageNode, 'page', []);
+        EventType.IMAGE_FRAME_UPDATED, imageNode, 'page', '', []);
     CommandHandler.onImageFrameUpdated_(event);
   } else {
     imageNode.getImageData(0, 0);
@@ -1495,5 +1496,4 @@
         result['sessionType'] === chrome.chromeosInfoPrivate.SessionType.KIOSK;
   });
 };
-
 });  // goog.scope
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/custom_automation_event.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/custom_automation_event.js
index 114b80c5..cbe1724 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/custom_automation_event.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/custom_automation_event.js
@@ -25,13 +25,16 @@
    * @param {chrome.automation.EventType} type The event type.
    * @param {!chrome.automation.AutomationNode} target The event target.
    * @param {string} eventFrom The source of this event.
+   * @param {string} eventFromAction The accessibility action that caused this
+   *     event.
    * @param {!Array<chrome.automation.AutomationIntent>} intents Intents for
    *     this event.
    */
-  constructor(type, target, eventFrom, intents) {
+  constructor(type, target, eventFrom, eventFromAction, intents) {
     this.type = type;
     this.target = target;
     this.eventFrom = eventFrom;
+    this.eventFromAction = eventFromAction;
     this.intents = intents;
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
index eacdb49..5121e7f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
@@ -16,6 +16,7 @@
 goog.require('editing.TextEditHandler');
 
 goog.scope(function() {
+const ActionType = chrome.automation.ActionType;
 const AutomationNode = chrome.automation.AutomationNode;
 const Dir = constants.Dir;
 const EventType = chrome.automation.EventType;
@@ -96,7 +97,8 @@
       chrome.automation.getFocus((function(focus) {
                                    if (focus) {
                                      const event = new CustomAutomationEvent(
-                                         EventType.FOCUS, focus, 'page', []);
+                                         EventType.FOCUS, focus, 'page',
+                                         ActionType.FOCUS, []);
                                      this.onFocus(event);
                                    }
                                  }).bind(this));
@@ -215,7 +217,8 @@
       selectionStart =
           AutomationUtil.getEditableRoot(selectionStart) || selectionStart;
       this.onEditableChanged_(new CustomAutomationEvent(
-          evt.type, selectionStart, evt.eventFrom, evt.intents));
+          evt.type, selectionStart, evt.eventFrom, evt.eventFromAction,
+          evt.intents));
     }
 
     // Non-editable selections are handled in |Background|.
@@ -272,7 +275,7 @@
     Output.forceModeForNextSpeechUtterance(QueueMode.CATEGORY_FLUSH);
 
     const event = new CustomAutomationEvent(
-        EventType.FOCUS, node, evt.eventFrom, evt.intents);
+        EventType.FOCUS, node, evt.eventFrom, evt.eventFromAction, evt.intents);
     this.onEventDefault(event);
 
     // Refresh the handler, if needed, now that ChromeVox focus is up to date.
@@ -600,8 +603,8 @@
     // after you close them.
     chrome.automation.getFocus(function(focus) {
       if (focus) {
-        const event =
-            new CustomAutomationEvent(EventType.FOCUS, focus, 'page', []);
+        const event = new CustomAutomationEvent(
+            EventType.FOCUS, focus, 'page', ActionType.FOCUS, []);
         this.onFocus(event);
       }
     }.bind(this));
@@ -768,5 +771,4 @@
  * @type {DesktopAutomationHandler}
  */
 DesktopAutomationHandler.instance;
-
 });  // goog.scope
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/next_earcons.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
similarity index 98%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/next_earcons.js
rename to chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
index 49e3845..dffd3b41 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/next_earcons.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
@@ -7,7 +7,7 @@
  * auditory cues.
  */
 
-export class NextEarcons extends AbstractEarcons {
+export class Earcons extends AbstractEarcons {
   constructor() {
     super();
 
@@ -39,7 +39,7 @@
    * @return {string} The human-readable name of the earcon set.
    */
   getName() {
-    return 'ChromeVox Next earcons';
+    return 'ChromeVox earcons';
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
index cba4772..78a1c11 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
@@ -193,8 +193,9 @@
     }
 
     Output.forceModeForNextSpeechUtterance(QueueMode.FLUSH);
-    DesktopAutomationHandler.instance.onEventDefault(
-        new CustomAutomationEvent(EventType.HOVER, target, '', []));
+    DesktopAutomationHandler.instance.onEventDefault(new CustomAutomationEvent(
+        EventType.HOVER, target, '', chrome.automation.ActionType.HIT_TEST,
+        []));
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
index d5b3fc9..4cf8816f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/range_automation_handler.js
@@ -194,7 +194,7 @@
 
     const event = new CustomAutomationEvent(
         EventType.CHECKED_STATE_CHANGED, evt.target, evt.eventFrom,
-        evt.intents);
+        evt.eventFromAction, evt.intents);
     this.onEventIfInRange(event);
   }
 
diff --git a/chrome/browser/resources/settings/languages_page/languages.js b/chrome/browser/resources/settings/languages_page/languages.js
index 34065a7..5138690 100644
--- a/chrome/browser/resources/settings/languages_page/languages.js
+++ b/chrome/browser/resources/settings/languages_page/languages.js
@@ -181,6 +181,8 @@
     'prospectiveUILanguageChanged_(prefs.intl.app_locale.value, languages)',
     'preferredLanguagesPrefChanged_(' +
         'prefs.' + preferredLanguagesPrefName + '.value, languages)',
+    'preferredLanguagesPrefChanged_(' +
+        'prefs.intl.forced_languages.value.*, languages)',
     'spellCheckDictionariesPrefChanged_(' +
         'prefs.spellcheck.dictionaries.value.*, ' +
         'prefs.spellcheck.forced_dictionaries.value.*, ' +
@@ -623,10 +625,13 @@
 
     const pref = this.getPref(preferredLanguagesPrefName);
     const enabledLanguageCodes = pref.value.split(',');
+    const languagesForcedPref = this.getPref('intl.forced_languages');
     const spellCheckPref = this.getPref('spellcheck.dictionaries');
     const spellCheckForcedPref = this.getPref('spellcheck.forced_dictionaries');
     const spellCheckBlockedPref =
         this.getPref('spellcheck.blocked_dictionaries');
+    const languageForcedSet = this.makeSetFromArray_(
+        /** @type {!Array<string>} */ (languagesForcedPref.value));
     const spellCheckSet = this.makeSetFromArray_(
         /** @type {!Array<string>} */ (
             spellCheckPref.value.concat(spellCheckForcedPref.value)));
@@ -640,6 +645,7 @@
         /** @type {!Array<string>} */ (translateBlockedPref.value));
 
     const enabledLanguageStates = [];
+
     for (let i = 0; i < enabledLanguageCodes.length; i++) {
       const code = enabledLanguageCodes[i];
       const language = this.supportedLanguageMap_.get(code);
@@ -657,6 +663,7 @@
           translateTarget, prospectiveUILanguage);
       languageState.isManaged =
           spellCheckForcedSet.has(code) || spellCheckBlockedSet.has(code);
+      languageState.isForced = languageForcedSet.has(code);
       languageState.downloadDictionaryFailureCount = 0;
       enabledLanguageStates.push(languageState);
     }
diff --git a/chrome/browser/resources/settings/languages_page/languages_subpage.html b/chrome/browser/resources/settings/languages_page/languages_subpage.html
index 398ebbc..884be09 100644
--- a/chrome/browser/resources/settings/languages_page/languages_subpage.html
+++ b/chrome/browser/resources/settings/languages_page/languages_subpage.html
@@ -158,4 +158,10 @@
       language-helper="[[languageHelper]]"
       on-close="onAddLanguagesDialogClose_">
   </settings-add-languages-dialog>
-</template>
\ No newline at end of file
+</template>
+<template is="dom-if" if="[[showManagedLanguageDialog_]]" restamp>
+  <managed-dialog on-close="onManagedLanguageDialogClosed_"
+      title="[[i18n('languageManagedDialogTitle')]]"
+      body="[[i18n('languageManagedDialogBody')]]">
+  </managed-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/languages_page/languages_subpage.js b/chrome/browser/resources/settings/languages_page/languages_subpage.js
index 2de4d55..cbe0c1c 100644
--- a/chrome/browser/resources/settings/languages_page/languages_subpage.js
+++ b/chrome/browser/resources/settings/languages_page/languages_subpage.js
@@ -6,6 +6,7 @@
  * @fileoverview 'settings-languages-page' is the settings page
  * for language and input method settings.
  */
+import 'chrome://resources/cr_components/managed_dialog/managed_dialog.js';
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
@@ -116,6 +117,12 @@
       },
     },
     // </if>
+
+    /** @private */
+    showManagedLanguageDialog_: {
+      type: Boolean,
+      value: false,
+    },
   },
 
   // <if expr="chromeos">
@@ -492,6 +499,11 @@
    */
   onMoveToTopTap_() {
     /** @type {!CrActionMenuElement} */ (this.$$('#menu').get()).close();
+    if (this.detailLanguage_.isForced) {
+      // If language is managed, show dialog to inform user it can't be modified
+      this.showManagedLanguageDialog_ = true;
+      return;
+    }
     this.languageHelper.moveLanguageToFront(this.detailLanguage_.language.code);
     this.languageSettingsMetricsProxy_.recordSettingsMetric(
         LanguageSettingsActionType.LANGUAGE_LIST_REORDERED);
@@ -503,6 +515,11 @@
    */
   onMoveUpTap_() {
     /** @type {!CrActionMenuElement} */ (this.$$('#menu').get()).close();
+    if (this.detailLanguage_.isForced) {
+      // If language is managed, show dialog to inform user it can't be modified
+      this.showManagedLanguageDialog_ = true;
+      return;
+    }
     this.languageHelper.moveLanguage(
         this.detailLanguage_.language.code, true /* upDirection */);
     this.languageSettingsMetricsProxy_.recordSettingsMetric(
@@ -515,6 +532,11 @@
    */
   onMoveDownTap_() {
     /** @type {!CrActionMenuElement} */ (this.$$('#menu').get()).close();
+    if (this.detailLanguage_.isForced) {
+      // If language is managed, show dialog to inform user it can't be modified
+      this.showManagedLanguageDialog_ = true;
+      return;
+    }
     this.languageHelper.moveLanguage(
         this.detailLanguage_.language.code, false /* upDirection */);
     this.languageSettingsMetricsProxy_.recordSettingsMetric(
@@ -527,6 +549,11 @@
    */
   onRemoveLanguageTap_() {
     /** @type {!CrActionMenuElement} */ (this.$$('#menu').get()).close();
+    if (this.detailLanguage_.isForced) {
+      // If language is managed, show dialog to inform user it can't be modified
+      this.showManagedLanguageDialog_ = true;
+      return;
+    }
     this.languageHelper.disableLanguage(this.detailLanguage_.language.code);
     this.languageSettingsMetricsProxy_.recordSettingsMetric(
         LanguageSettingsActionType.LANGUAGE_REMOVED);
@@ -587,4 +614,12 @@
       }
     }, kMenuCloseDelay);
   },
+
+  /**
+   * Triggered when the managed language dialog is dismissed.
+   * @private
+   */
+  onManagedLanguageDialogClosed_() {
+    this.showManagedLanguageDialog_ = false;
+  }
 });
diff --git a/chrome/browser/resources/settings/languages_page/languages_types.js b/chrome/browser/resources/settings/languages_page/languages_types.js
index 099592a..7dc119f90 100644
--- a/chrome/browser/resources/settings/languages_page/languages_types.js
+++ b/chrome/browser/resources/settings/languages_page/languages_types.js
@@ -15,6 +15,7 @@
  *   spellCheckEnabled: boolean,
  *   translateEnabled: boolean,
  *   isManaged: boolean,
+ *   isForced: boolean,
  *   downloadDictionaryFailureCount: number,
  *   downloadDictionaryStatus:
  *       ?chrome.languageSettingsPrivate.SpellcheckDictionaryStatus,
diff --git a/chrome/browser/sessions/tab_restore_service_browsertest.cc b/chrome/browser/sessions/tab_restore_service_browsertest.cc
index d10c990..32f1f2e 100644
--- a/chrome/browser/sessions/tab_restore_service_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_service_browsertest.cc
@@ -10,13 +10,27 @@
 
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sessions/tab_restore_service_factory.h"
+#include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/test/test_system_web_app_installation.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/browser_test.h"
 
 using Window = sessions::TabRestoreService::Window;
 
-using TabRestoreServiceImplBrowserTest = InProcessBrowserTest;
+class TabRestoreServiceImplBrowserTest : public InProcessBrowserTest {
+ public:
+  TabRestoreServiceImplBrowserTest()
+      : test_system_web_app_installation_(
+            web_app::TestSystemWebAppInstallation::
+                SetUpTabbedMultiWindowApp()) {}
+
+ protected:
+  std::unique_ptr<web_app::TestSystemWebAppInstallation>
+      test_system_web_app_installation_;
+};
 
 IN_PROC_BROWSER_TEST_F(TabRestoreServiceImplBrowserTest, RestoreApp) {
   Profile* profile = browser()->profile();
@@ -38,4 +52,38 @@
   EXPECT_EQ(app_name, restored_window->app_name);
 }
 
+// Test that the last app browser tab saves the Window to ensure it will be
+// reopened in the correct app browser.
+IN_PROC_BROWSER_TEST_F(TabRestoreServiceImplBrowserTest,
+                       LastAppTabSavesWindow) {
+  Profile* profile = browser()->profile();
+  sessions::TabRestoreService* trs =
+      TabRestoreServiceFactory::GetForProfile(profile);
+
+  test_system_web_app_installation_->WaitForAppInstall();
+  Browser* app_browser = web_app::LaunchWebAppBrowser(
+      browser()->profile(), test_system_web_app_installation_->GetAppId());
+  GURL app_url = test_system_web_app_installation_->GetAppUrl();
+  ui_test_utils::NavigateToURL(app_browser, app_url);
+
+  // Create second tab and close it, TAB entry should be created.
+  chrome::NewTab(app_browser);
+  ui_test_utils::NavigateToURL(app_browser, app_url);
+  chrome::CloseTab(app_browser);
+  ASSERT_EQ(1U, trs->entries().size());
+  const sessions::TabRestoreService::Entry* tab_entry =
+      trs->entries().front().get();
+  EXPECT_EQ(sessions::TabRestoreService::TAB, tab_entry->type);
+
+  // Close last tab, WINDOW entry should be created.
+  chrome::CloseTab(app_browser);
+  EXPECT_EQ(2U, trs->entries().size());
+  const sessions::TabRestoreService::Entry* window_entry =
+      trs->entries().front().get();
+  ASSERT_EQ(sessions::TabRestoreService::WINDOW, window_entry->type);
+  const Window* restored_window = static_cast<const Window*>(window_entry);
+  EXPECT_EQ(app_browser->app_name(), restored_window->app_name);
+  EXPECT_EQ(1U, restored_window->tabs.size());
+}
+
 #endif  // defined(USE_AURA)
diff --git a/chrome/browser/sharesheet/share_action.cc b/chrome/browser/sharesheet/share_action.cc
index 9dc3f8e4..9864649 100644
--- a/chrome/browser/sharesheet/share_action.cc
+++ b/chrome/browser/sharesheet/share_action.cc
@@ -22,4 +22,8 @@
 #endif
 }
 
+bool ShareAction::OnAcceleratorPressed(const ui::Accelerator& accelerator) {
+  return false;
+}
+
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/share_action.h b/chrome/browser/sharesheet/share_action.h
index f695a5a2..d2a84d1d 100644
--- a/chrome/browser/sharesheet/share_action.h
+++ b/chrome/browser/sharesheet/share_action.h
@@ -9,6 +9,7 @@
 
 #include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/views/view.h"
 
 namespace gfx {
@@ -54,6 +55,11 @@
   // hosted document.
   virtual bool ShouldShowAction(const apps::mojom::IntentPtr& intent,
                                 bool contains_hosted_document);
+
+  // Invoked when the accelerator has been pressed.
+  // ShareAction should return true if the accelerator has been processed and
+  // false otherwise. If not processed, the Sharesheet will close.
+  virtual bool OnAcceleratorPressed(const ui::Accelerator& accelerator);
 };
 
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc
index ed60791..437a562 100644
--- a/chrome/browser/sharesheet/sharesheet_service.cc
+++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -116,6 +116,19 @@
   }
 }
 
+bool SharesheetService::OnAcceleratorPressed(
+    const ui::Accelerator& accelerator,
+    const std::u16string& active_action) {
+  if (active_action.empty())
+    return false;
+  ShareAction* share_action =
+      sharesheet_action_cache_->GetActionFromName(active_action);
+  DCHECK(share_action);
+  return share_action == nullptr
+             ? false
+             : share_action->OnAcceleratorPressed(accelerator);
+}
+
 SharesheetServiceDelegate* SharesheetService::GetOrCreateDelegate(
     gfx::NativeWindow native_window) {
   auto* delegate = GetDelegate(native_window);
diff --git a/chrome/browser/sharesheet/sharesheet_service.h b/chrome/browser/sharesheet/sharesheet_service.h
index a89ac81..0ce0bc7 100644
--- a/chrome/browser/sharesheet/sharesheet_service.h
+++ b/chrome/browser/sharesheet/sharesheet_service.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/gfx/native_widget_types.h"
 
 class Profile;
@@ -71,6 +72,8 @@
                         const TargetType type,
                         apps::mojom::IntentPtr intent,
                         views::View* share_action_view);
+  bool OnAcceleratorPressed(const ui::Accelerator& accelerator,
+                            const std::u16string& active_action);
   SharesheetServiceDelegate* GetOrCreateDelegate(
       gfx::NativeWindow native_window);
   SharesheetServiceDelegate* GetDelegate(gfx::NativeWindow native_window);
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.cc b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
index fe7cb980..685bb023 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.cc
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
@@ -49,10 +49,6 @@
   // Therefore there is no need to set is_bubble_open_ to false.
 }
 
-void SharesheetServiceDelegate::OnActionLaunched() {
-  sharesheet_bubble_view_->ShowActionView();
-}
-
 void SharesheetServiceDelegate::OnTargetSelected(
     const std::u16string& target_name,
     const TargetType type,
@@ -62,6 +58,21 @@
                                         std::move(intent), share_action_view);
 }
 
+bool SharesheetServiceDelegate::OnAcceleratorPressed(
+    const ui::Accelerator& accelerator,
+    const std::u16string& active_action) {
+  return sharesheet_service_->OnAcceleratorPressed(accelerator, active_action);
+}
+
+void SharesheetServiceDelegate::OnActionLaunched() {
+  sharesheet_bubble_view_->ShowActionView();
+}
+
+const gfx::VectorIcon* SharesheetServiceDelegate::GetVectorIcon(
+    const std::u16string& display_name) {
+  return sharesheet_service_->GetVectorIcon(display_name);
+}
+
 gfx::NativeWindow SharesheetServiceDelegate::GetNativeWindow() {
   return native_window_;
 }
@@ -81,9 +92,4 @@
   sharesheet_bubble_view_->CloseBubble();
 }
 
-const gfx::VectorIcon* SharesheetServiceDelegate::GetVectorIcon(
-    const std::u16string& display_name) {
-  return sharesheet_service_->GetVectorIcon(display_name);
-}
-
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.h b/chrome/browser/sharesheet/sharesheet_service_delegate.h
index afad362..6974dba9 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.h
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/gfx/native_widget_types.h"
 
 class Profile;
@@ -48,6 +49,8 @@
                         const TargetType type,
                         apps::mojom::IntentPtr intent,
                         views::View* share_action_view);
+  bool OnAcceleratorPressed(const ui::Accelerator& accelerator,
+                            const std::u16string& active_action);
   void OnActionLaunched();
   const gfx::VectorIcon* GetVectorIcon(const std::u16string& display_name);
   gfx::NativeWindow GetNativeWindow();
diff --git a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
index 871aeb9..99b07104 100644
--- a/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
+++ b/chrome/browser/spellchecker/spellcheck_service_browsertest.cc
@@ -768,7 +768,7 @@
 // spellcheck language preferences for the test profile.
 IN_PROC_BROWSER_TEST_F(SpellcheckServiceWindowsHybridBrowserTestDelayInit,
                        PRE_WindowsHybridSpellcheckDelayInit) {
-  GetPrefs()->SetString(language::prefs::kAcceptLanguages, kAcceptLanguages);
+  GetPrefs()->SetString(language::prefs::kSelectedLanguages, kAcceptLanguages);
   base::Value spellcheck_dictionaries_list(base::Value::Type::LIST);
   for (const auto& dictionary : kSpellcheckDictionariesBefore) {
     spellcheck_dictionaries_list.Append(std::move(dictionary));
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/SadTab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/SadTab.java
index 821e7eeb..d284bd0 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/SadTab.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/SadTab.java
@@ -77,9 +77,9 @@
 
         // Make sure we are not adding the "Aw, snap" view over an existing one.
         assert mView == null;
+        mSadTabSuccessiveRefreshCounter++;
         mView = createView(context, suggestionAction, buttonAction, showSendFeedbackView(),
                 mTab.isIncognito());
-        mSadTabSuccessiveRefreshCounter++;
 
         mTab.getTabViewManager().addTabViewProvider(this);
     }
@@ -90,7 +90,7 @@
     public boolean showSendFeedbackView() {
         // If the tab has crashed twice in a row change the sad tab view to the "Send Feedback"
         // version and change the onClickListener.
-        return mSadTabSuccessiveRefreshCounter >= 1;
+        return mSadTabSuccessiveRefreshCounter >= 2;
     }
 
     /**
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index 41b5035..7be439dd 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -371,7 +371,7 @@
 
   TranslateBridge::PrependToAcceptLanguagesIfNecessary(locale_string,
                                                        &accept_languages);
-  GetPrefService()->SetString(language::prefs::kAcceptLanguages,
+  GetPrefService()->SetString(language::prefs::kSelectedLanguages,
                               accept_languages);
 }
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 8a40bcc..28ca4bcd 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3525,6 +3525,11 @@
         Feed card menu is closed
       </message>
 
+      <!-- WebFeed -->
+      <message name="IDS_WEB_FEED_FOLLOW_LOADING_DESCRIPTION" desc="The content description of the loading spinner after the user presses Follow and is waiting for a response.">
+        Following...
+      </message>
+
       <!-- Storage Preference UI strings for clearing storage. -->
       <message name="IDS_STORAGE_MANAGEMENT_ACTIVITY_LABEL" desc="Title for Chrome's Manage Space Activity.">
          Google <ph name="APP_NAME">%1$s<ex>Chrome</ex></ph> storage
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_LOADING_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_LOADING_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..e47788f
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_LOADING_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+55db6464a112b5c9476e9b8d11f2610fc1a814a7
\ No newline at end of file
diff --git a/chrome/browser/ui/app_list/search/files/file_result.cc b/chrome/browser/ui/app_list/search/files/file_result.cc
index 072ca3c..e951836f 100644
--- a/chrome/browser/ui/app_list/search/files/file_result.cc
+++ b/chrome/browser/ui/app_list/search/files/file_result.cc
@@ -67,6 +67,12 @@
     case ResultType::kZeroStateFile:
       SetMetricsType(ash::ZERO_STATE_FILE);
       break;
+    case ResultType::kLocalFile:
+      SetMetricsType(ash::LOCAL_FILE_SEARCH);
+      break;
+    case ResultType::kDriveFile:
+      SetMetricsType(ash::DRIVE_FILE_SEARCH);
+      break;
     default:
       NOTREACHED();
   }
diff --git a/chrome/browser/ui/app_list/search/files/local_file_provider.cc b/chrome/browser/ui/app_list/search/files/local_file_provider.cc
index 66b0322d..f6b5c77 100644
--- a/chrome/browser/ui/app_list/search/files/local_file_provider.cc
+++ b/chrome/browser/ui/app_list/search/files/local_file_provider.cc
@@ -4,11 +4,56 @@
 
 #include "chrome/browser/ui/app_list/search/files/local_file_provider.h"
 
-#include "ash/public/cpp/app_list/app_list_types.h"
+#include <cctype>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "chrome/browser/ui/app_list/search/files/file_result.h"
 
 namespace app_list {
+namespace {
+
+constexpr char kLocalFileSchema[] = "local_file://";
+constexpr int kMaxResults = 25;
+// The default relevance should only be used as a fallback.
+// TODO(crbug.com/1154513): Log error histograms whenever this needs to be used.
+constexpr double kDefaultRelevance = 0.5;
+
+// Construct a case-insensitive fnmatch query from |query|. E.g. for abc123, the
+// result would be *[aA][bB][cC]123*.
+std::string CreateFnmatchQuery(const std::string& query) {
+  std::vector<std::string> query_pieces = {"*"};
+  size_t sequence_start = 0;
+  for (size_t i = 0; i < query.size(); ++i) {
+    if (isalpha(query[i])) {
+      if (sequence_start != i) {
+        query_pieces.push_back(
+            query.substr(sequence_start, i - sequence_start));
+      }
+      std::string piece("[");
+      piece.resize(4);
+      piece[1] = tolower(query[i]);
+      piece[2] = toupper(query[i]);
+      piece[3] = ']';
+      query_pieces.push_back(std::move(piece));
+      sequence_start = i + 1;
+    }
+  }
+  if (sequence_start != query.size()) {
+    query_pieces.push_back(query.substr(sequence_start));
+  }
+  query_pieces.push_back("*");
+
+  return base::StrCat(query_pieces);
+}
+
+}  // namespace
 
 LocalFileProvider::LocalFileProvider(Profile* profile) : profile_(profile) {
   DCHECK(profile_);
@@ -16,12 +61,50 @@
 
 LocalFileProvider::~LocalFileProvider() = default;
 
-void LocalFileProvider::Start(const std::u16string& query) {
-  // TODO(crbug.com/1154513): Search for local files.
-}
-
 ash::AppListSearchResultType LocalFileProvider::ResultType() {
   return ash::AppListSearchResultType::kLocalFile;
 }
 
+void LocalFileProvider::Start(const std::u16string& query) {
+  // Clear results and cancel any outgoing requests.
+  ClearResultsSilently();
+  weak_factory_.InvalidateWeakPtrs();
+
+  // This provider does not handle zero-state.
+  if (query.empty())
+    return;
+
+  base::ThreadPool::PostTask(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+      base::BindOnce(&LocalFileProvider::SearchFilesByPattern,
+                     weak_factory_.GetWeakPtr(), base::UTF16ToUTF8(query)));
+}
+
+void LocalFileProvider::SearchFilesByPattern(const std::string& query) {
+  base::FileEnumerator enumerator(
+      file_manager::util::GetMyFilesFolderForProfile(profile_),
+      /*recursive=*/true, base::FileEnumerator::FILES,
+      CreateFnmatchQuery(query), base::FileEnumerator::FolderSearchPolicy::ALL);
+  SearchProvider::Results results;
+
+  for (base::FilePath path = enumerator.Next(); !path.empty();
+       path = enumerator.Next()) {
+    results.emplace_back(MakeResult(path));
+
+    // TODO(crbug.com/1154513): Also exit early if we reach a timeout.
+    if (results.size() == kMaxResults)
+      break;
+  }
+  SwapResults(&results);
+  // TODO(crbug.com/1154513): Log success and latency histograms.
+}
+
+std::unique_ptr<FileResult> LocalFileProvider::MakeResult(
+    const base::FilePath& path) {
+  // TODO(crbug.com/1154513): Set the result's fuzzy match relevance.
+  return std::make_unique<FileResult>(
+      kLocalFileSchema, path, ash::AppListSearchResultType::kLocalFile,
+      ash::SearchResultDisplayType::kList, kDefaultRelevance, profile_);
+}
+
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/files/local_file_provider.h b/chrome/browser/ui/app_list/search/files/local_file_provider.h
index 3a5f291..2db03ac 100644
--- a/chrome/browser/ui/app_list/search/files/local_file_provider.h
+++ b/chrome/browser/ui/app_list/search/files/local_file_provider.h
@@ -5,12 +5,16 @@
 #ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_LOCAL_FILE_PROVIDER_H_
 #define CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_LOCAL_FILE_PROVIDER_H_
 
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
 
 class Profile;
 
 namespace app_list {
 
+class FileResult;
+
 class LocalFileProvider : public SearchProvider {
  public:
   explicit LocalFileProvider(Profile* profile);
@@ -20,11 +24,16 @@
   LocalFileProvider& operator=(const LocalFileProvider&) = delete;
 
   // SearchProvider:
-  void Start(const std::u16string& query) override;
   ash::AppListSearchResultType ResultType() override;
+  void Start(const std::u16string& query) override;
 
  private:
+  void SearchFilesByPattern(const std::string& query);
+  std::unique_ptr<FileResult> MakeResult(const base::FilePath& path);
+
   Profile* const profile_;
+
+  base::WeakPtrFactory<LocalFileProvider> weak_factory_{this};
 };
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
index a606b76..3ab1e3d1 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
@@ -150,6 +150,7 @@
       this, views::Widget::GetWidgetForNativeWindow(native_window));
   parent_view_ =
       views::Widget::GetWidgetForNativeWindow(native_window)->GetRootView();
+  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
   UpdateAnchorPosition();
 
   CreateBubble();
@@ -427,20 +428,36 @@
   }
 }
 
-void SharesheetBubbleView::OnKeyEvent(ui::KeyEvent* event) {
-  // Ignore key press if bubble is closing.
-  // TODO(crbug.com/1141741) Update to OnKeyPressed.
-  if (!IsKeyboardCodeArrow(event->key_code()) ||
-      event->type() != ui::ET_KEY_RELEASED || default_view_ == nullptr ||
+bool SharesheetBubbleView::AcceleratorPressed(
+    const ui::Accelerator& accelerator) {
+  // We override this because when this is handled by the base class,
+  // OnKeyPressed is not invoked when a user presses |VKEY_ESCAPE| if they have
+  // not pressed |VKEY_TAB| first to focus the SharesheetBubbleView.
+  DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
+  if (share_action_view_->GetVisible() &&
+      delegate_->OnAcceleratorPressed(accelerator, active_target_)) {
+    return true;
+  }
+  escape_pressed_ = true;
+  sharesheet::SharesheetMetrics::RecordSharesheetActionMetrics(
+      sharesheet::SharesheetMetrics::UserAction::kCancelledThroughEscPress);
+  CloseWidgetWithAnimateFadeOut(views::Widget::ClosedReason::kEscKeyPressed);
+
+  return true;
+}
+
+bool SharesheetBubbleView::OnKeyPressed(const ui::KeyEvent& event) {
+  // Ignore key press if it's not an arrow or bubble is closing.
+  if (!IsKeyboardCodeArrow(event.key_code()) || default_view_ == nullptr ||
       is_bubble_closing_) {
-    if (event->key_code() == ui::VKEY_ESCAPE && !is_bubble_closing_)
+    if (event.key_code() == ui::VKEY_ESCAPE && !is_bubble_closing_) {
       escape_pressed_ = true;
-    View::OnKeyEvent(event);
-    return;
+    }
+    return false;
   }
 
   int delta = 0;
-  switch (event->key_code()) {
+  switch (event.key_code()) {
     case ui::VKEY_UP:
       delta = -kMaxTargetsPerRow;
       break;
@@ -473,8 +490,7 @@
     expanded_view_->children()[keyboard_highlighted_target_ + 1 - default_views]
         ->RequestFocus();
   }
-
-  View::OnKeyEvent(event);
+  return true;
 }
 
 ax::mojom::Role SharesheetBubbleView::GetAccessibleWindowRole() {
@@ -510,12 +526,16 @@
     if (close_callback_) {
       std::move(close_callback_).Run(sharesheet::SharesheetResult::kCancel);
     }
-    auto action = escape_pressed_ ? sharesheet::SharesheetMetrics::UserAction::
-                                        kCancelledThroughEscPress
-                                  : sharesheet::SharesheetMetrics::UserAction::
-                                        kCancelledThroughClickingOut;
-    sharesheet::SharesheetMetrics::RecordSharesheetActionMetrics(action);
-    CloseWidgetWithAnimateFadeOut(views::Widget::ClosedReason::kLostFocus);
+    auto user_action =
+        sharesheet::SharesheetMetrics::UserAction::kCancelledThroughClickingOut;
+    auto closed_reason = views::Widget::ClosedReason::kLostFocus;
+    if (escape_pressed_) {
+      user_action =
+          sharesheet::SharesheetMetrics::UserAction::kCancelledThroughEscPress;
+      closed_reason = views::Widget::ClosedReason::kEscKeyPressed;
+    }
+    sharesheet::SharesheetMetrics::RecordSharesheetActionMetrics(user_action);
+    CloseWidgetWithAnimateFadeOut(closed_reason);
   }
 }
 
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
index 4b148251..13b2a46 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h
@@ -51,8 +51,11 @@
  private:
   class SharesheetParentWidgetObserver;
 
+  // ui::AcceleratorTarget:
+  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
+
   // ui::EventHandler:
-  void OnKeyEvent(ui::KeyEvent* event) override;
+  bool OnKeyPressed(const ui::KeyEvent& event) override;
 
   // views::WidgetDelegate:
   ax::mojom::Role GetAccessibleWindowRole() override;
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index ac9e300..e9781479 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -474,10 +474,6 @@
   // TODO(crbug/1072058): Check user preferences before showing intent picker.
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   for (const auto& match : url_handler_matches) {
-    if (match.profile_path.empty() || match.app_id.empty() ||
-        !match.url.is_valid()) {
-      continue;
-    }
     // Do not load profile if profile path is not valid.
     if (!profile_manager->GetProfileAttributesStorage()
              .GetProfileAttributesWithPath(match.profile_path)) {
@@ -823,7 +819,7 @@
     silent_launch = true;
   }
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
   if (base::FeatureList::IsEnabled(features::kOnConnectNative) &&
       command_line.HasSwitch(switches::kNativeMessagingConnectHost) &&
       command_line.HasSwitch(switches::kNativeMessagingConnectExtension)) {
diff --git a/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
index 56d8be3..5088401c 100644
--- a/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/custom_tab_bar_view_browsertest.cc
@@ -256,20 +256,12 @@
 
 // Check the custom tab bar is not instantiated for a popup window.
 IN_PROC_BROWSER_TEST_F(CustomTabBarViewBrowserTest, IsNotCreatedInPopup) {
-#if defined(OS_LINUX)
-  {
-    auto* command_line = base::CommandLine::ForCurrentProcess();
-    if (command_line->HasSwitch(switches::kOzonePlatform) &&
-        command_line->GetSwitchValueASCII(switches::kOzonePlatform) ==
-            "wayland") {
-      // TODO(crbug.com/1179071): Test is flaky on Linux Wayland configuration.
-      GTEST_SKIP() << "Flaky on Linux Wayland";
-    }
-  }
-#endif
+  EXPECT_TRUE(NavigateToURL(browser_view_->GetActiveWebContents(),
+                            GURL(url::kAboutBlankURL)));
 
-  Browser* popup = OpenPopup(browser_view_->GetActiveWebContents(),
-                             GURL("http://example.com"));
+  Browser* popup =
+      OpenPopup(browser_view_->GetActiveWebContents(),
+                https_server()->GetURL("app.com", "/ssl/google.html"));
   EXPECT_TRUE(popup);
 
   BrowserView* popup_view = BrowserView::GetBrowserViewForBrowser(popup);
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
index e9f16212..6f748db2 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.cc
@@ -7,6 +7,8 @@
 #include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/flag_descriptions.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace {
 
@@ -25,8 +27,7 @@
 // TODO(elainechien): Explore better ways to allow developers to add their
 // experiments.
 // Experiments featured in labs must have feature entries of type FEATURE_VALUE
-// (Default, Enabled, Disabled states). Experiments with multiple parameters may
-// be considered in the future.
+// (Default, Enabled, Disabled states) or FEATURE_WITH_PARAMS_VALUE
 const std::vector<LabInfo>& GetData() {
   if (GetTestData())
     return GetTestData().value();
@@ -36,27 +37,31 @@
 
     // Read Later.
     lab_info.emplace_back(LabInfo(
-        flag_descriptions::kReadLaterFlagId, base::ASCIIToUTF16("Reading List"),
-        base::ASCIIToUTF16("Right click on a tab or click the Bookmark icon to "
-                           "add tabs to a reading "
-                           "list. Access from the Bookmarks bar."),
+        flag_descriptions::kReadLaterFlagId,
+        l10n_util::GetStringUTF16(IDS_READ_LATER_EXPERIMENT_NAME),
+        l10n_util::GetStringUTF16(IDS_READ_LATER_EXPERIMENT_DESCRIPTION),
         "chrome-labs-read-later", version_info::Channel::BETA));
 
     // Tab Scrolling.
-    lab_info.emplace_back(
-        LabInfo(flag_descriptions::kScrollableTabStripFlagId,
-                base::ASCIIToUTF16("Tab Scrolling"),
-                base::ASCIIToUTF16(
-                    "Enables tab strip to scroll left and right when full."),
-                "chrome-labs-tab-scrolling", version_info::Channel::BETA));
+    std::vector<std::u16string> tab_scrolling_variation_descriptions = {
+        l10n_util::GetStringUTF16(IDS_TABS_SHRINK_TO_PINNED_TAB_WIDTH),
+        l10n_util::GetStringUTF16(IDS_TABS_SHRINK_TO_MEDIUM_WIDTH),
+        l10n_util::GetStringUTF16(IDS_TABS_SHRINK_TO_LARGE_WIDTH),
+        l10n_util::GetStringUTF16(IDS_TABS_DO_NOT_SHRINK)};
+
+    lab_info.emplace_back(LabInfo(
+        flag_descriptions::kScrollableTabStripFlagId,
+        l10n_util::GetStringUTF16(IDS_TAB_SCROLLING_EXPERIMENT_NAME),
+        l10n_util::GetStringUTF16(IDS_TAB_SCROLLING_EXPERIMENT_DESCRIPTION),
+        "chrome-labs-tab-scrolling", version_info::Channel::BETA,
+        tab_scrolling_variation_descriptions));
 
     // Tab Search.
-    lab_info.emplace_back(
-        LabInfo(flag_descriptions::kEnableTabSearchFlagId,
-                base::ASCIIToUTF16("Tab Search"),
-                base::ASCIIToUTF16("Enable a popup bubble in Top Chrome UI to "
-                                   "search over currently open tabs."),
-                "chrome-labs-tab-search", version_info::Channel::BETA));
+    lab_info.emplace_back(LabInfo(
+        flag_descriptions::kEnableTabSearchFlagId,
+        l10n_util::GetStringUTF16(IDS_TAB_SEARCH_EXPERIMENT_NAME),
+        l10n_util::GetStringUTF16(IDS_TAB_SEARCH_EXPERIMENT_DESCRIPTION),
+        "chrome-labs-tab-search", version_info::Channel::BETA));
 
     return lab_info;
   }());
@@ -65,16 +70,20 @@
 }
 }  // namespace
 
-LabInfo::LabInfo(const std::string& internal_name,
-                 const std::u16string& visible_name,
-                 const std::u16string& visible_description,
-                 const std::string& feedback_category_name,
-                 version_info::Channel allowed_channel)
+LabInfo::LabInfo(
+    const std::string& internal_name,
+    const std::u16string& visible_name,
+    const std::u16string& visible_description,
+    const std::string& feedback_category_name,
+    version_info::Channel allowed_channel,
+    std::vector<std::u16string> translated_feature_variation_descriptions)
     : internal_name(internal_name),
       visible_name(visible_name),
       visible_description(visible_description),
       feedback_category_name(feedback_category_name),
-      allowed_channel(allowed_channel) {}
+      allowed_channel(allowed_channel),
+      translated_feature_variation_descriptions(
+          translated_feature_variation_descriptions) {}
 
 LabInfo::LabInfo(const LabInfo& other) = default;
 
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h
index b6095be1..5a0f18c9 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h
@@ -9,16 +9,15 @@
 #include <vector>
 #include "components/version_info/channel.h"
 
-// Currently there are differences in both visible name and visible description
-// between about_flags and what we want for Chrome Labs. We are coordinating to
-// match these. Visible name and visible description can be removed from this
-// struct after that.
 struct LabInfo {
-  LabInfo(const std::string& internal_name,
-          const std::u16string& visible_name,
-          const std::u16string& visible_description,
-          const std::string& feedback_category_name,
-          version_info::Channel allowed_channel);
+  LabInfo(
+      const std::string& internal_name,
+      const std::u16string& visible_name,
+      const std::u16string& visible_description,
+      const std::string& feedback_category_name,
+      version_info::Channel allowed_channel,
+      std::vector<std::u16string> translated_feature_variation_descriptions =
+          std::vector<std::u16string>());
   LabInfo(const LabInfo& other);
   ~LabInfo();
   std::string internal_name;
@@ -29,6 +28,7 @@
   // considered allowed. ex) if BETA is specified, this feature will also be
   // shown on CANARY and DEV.
   version_info::Channel allowed_channel;
+  std::vector<std::u16string> translated_feature_variation_descriptions;
 };
 
 class ChromeLabsBubbleViewModel {
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model_unittest.cc
index 929769b..047f4d9 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model_unittest.cc
@@ -3,11 +3,25 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_model.h"
+#include "base/i18n/case_conversion.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/test/icu_test_util.h"
 #include "chrome/browser/about_flags.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/flags_ui/feature_entry.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+std::string CanonicalizeString(std::string original_string) {
+  std::string new_string;
+  // Trim common separator character used in about_flags
+  char kSeparators[] = "-";
+  base::RemoveChars(original_string, kSeparators, &new_string);
+  return ToLowerASCII(TrimWhitespaceASCII(new_string, base::TRIM_ALL));
+}
+}  // namespace
+
 class ChromeLabsBubbleViewModelTest : public ChromeViewsTestBase {};
 
 TEST_F(ChromeLabsBubbleViewModelTest, CheckFeaturesHaveSupportedTypes) {
@@ -24,3 +38,33 @@
                     flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE);
   }
 }
+
+// Experiments in Chrome Labs must features of type
+// FEATURE_WITH_PARAMS_VALUE must have variation descriptions in Chrome Labs
+// match those declared in about_flags.
+TEST_F(ChromeLabsBubbleViewModelTest, CheckFeatureWithParamsVariations) {
+  base::test::ScopedRestoreICUDefaultLocale locale(std::string("en_US"));
+
+  std::unique_ptr<ChromeLabsBubbleViewModel> model =
+      std::make_unique<ChromeLabsBubbleViewModel>();
+  const std::vector<LabInfo>& all_labs = model->GetLabInfo();
+  for (const auto& lab : all_labs) {
+    const flags_ui::FeatureEntry* entry =
+        about_flags::GetCurrentFlagsState()->FindFeatureEntryByName(
+            lab.internal_name);
+    if (entry->type == flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE) {
+      std::vector<std::u16string> translated_descriptions =
+          lab.translated_feature_variation_descriptions;
+      base::span<const flags_ui::FeatureEntry::FeatureVariation>
+          feature_entry_descriptions = entry->feature.feature_variations;
+      EXPECT_EQ(feature_entry_descriptions.size(),
+                translated_descriptions.size());
+      for (int i = 0; i < static_cast<int>(translated_descriptions.size());
+           i++) {
+        EXPECT_EQ(
+            CanonicalizeString(base::UTF16ToUTF8(translated_descriptions[i])),
+            CanonicalizeString(feature_entry_descriptions[i].description_text));
+      }
+    }
+  }
+}
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
index 0b6a3f61..f709472 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_bubble_view_unittest.cc
@@ -26,7 +26,7 @@
 
 namespace {
 const char kFirstTestFeatureId[] = "feature-1";
-const char kSecondTestFeatureId[] = "feature-2";
+const char kTestFeatureWithVariationId[] = "feature-2";
 const char kThirdTestFeatureId[] = "feature-3";
 const char kExpiredFlagTestFeatureId[] = "expired-feature";
 
@@ -54,7 +54,7 @@
             {{kFirstTestFeatureId, "", "",
               flags_ui::FlagsState::GetCurrentPlatform(),
               FEATURE_VALUE_TYPE(kTestFeature1)},
-             {kSecondTestFeatureId, "", "",
+             {kTestFeatureWithVariationId, "", "",
               flags_ui::FlagsState::GetCurrentPlatform(),
               FEATURE_WITH_PARAMS_VALUE_TYPE(kTestFeature2,
                                              kTestVariations2,
@@ -164,9 +164,13 @@
         LabInfo(kFirstTestFeatureId, base::ASCIIToUTF16(""),
                 base::ASCIIToUTF16(""), "", version_info::Channel::STABLE));
 
+    std::vector<std::u16string> variation_descriptions = {
+        base::ASCIIToUTF16("Description")};
+
     test_feature_info.emplace_back(
-        LabInfo(kSecondTestFeatureId, base::ASCIIToUTF16(""),
-                base::ASCIIToUTF16(""), "", version_info::Channel::STABLE));
+        LabInfo(kTestFeatureWithVariationId, base::ASCIIToUTF16(""),
+                base::ASCIIToUTF16(""), "", version_info::Channel::STABLE,
+                variation_descriptions));
 
     test_feature_info.emplace_back(
         LabInfo(kThirdTestFeatureId, base::ASCIIToUTF16(""),
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
index 2f29c67..52f8ec4 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
@@ -39,21 +39,54 @@
 
 class LabsComboboxModel : public ui::ComboboxModel {
  public:
-  explicit LabsComboboxModel(const flags_ui::FeatureEntry* feature_entry,
+  explicit LabsComboboxModel(const LabInfo& lab,
+                             const flags_ui::FeatureEntry* feature_entry,
                              int default_index)
-      : feature_entry_(feature_entry), default_index_(default_index) {}
+      : lab_(lab),
+        feature_entry_(feature_entry),
+        default_index_(default_index) {}
 
   // ui::ComboboxModel:
   int GetItemCount() const override { return feature_entry_->NumOptions(); }
 
+  // The order in which these descriptions are returned is the same in
+  // flags_ui::FeatureEntry::DescriptionForOption(..). If there are changes to
+  // this, the same changes must be made in
+  // flags_ui::FeatureEntry::DescriptionForOption(..).
   std::u16string GetItemAt(int index) const override {
-    // TODO(elainechien): remove white space for description
-    return feature_entry_->DescriptionForOption(index);
+    DCHECK_LT(index, feature_entry_->NumOptions());
+    int description_translation_id = IDS_CHROMELABS_DEFAULT;
+    if (feature_entry_->type ==
+        flags_ui::FeatureEntry::FEATURE_WITH_PARAMS_VALUE) {
+      if (index == 0) {
+        description_translation_id = IDS_CHROMELABS_DEFAULT;
+      } else if (index == 1) {
+        description_translation_id = IDS_CHROMELABS_ENABLED;
+      } else if (index < feature_entry_->NumOptions() - 1) {
+        // First two options do not have variations params.
+        int variation_index = index - 2;
+        return l10n_util::GetStringFUTF16(
+            IDS_CHROMELABS_ENABLED_WITH_VARIATION_NAME,
+            lab_.translated_feature_variation_descriptions[variation_index]);
+      } else {
+        DCHECK_EQ(feature_entry_->NumOptions() - 1, index);
+        description_translation_id = IDS_CHROMELABS_DISABLED;
+      }
+    } else {
+      const int kEnableDisableDescriptions[] = {
+          IDS_CHROMELABS_DEFAULT,
+          IDS_CHROMELABS_ENABLED,
+          IDS_CHROMELABS_DISABLED,
+      };
+      description_translation_id = kEnableDisableDescriptions[index];
+    }
+    return l10n_util::GetStringUTF16(description_translation_id);
   }
 
   int GetDefaultIndex() const override { return default_index_; }
 
  private:
+  const LabInfo& lab_;
   const flags_ui::FeatureEntry* feature_entry_;
   int default_index_;
 };
@@ -102,8 +135,10 @@
           .AddChildren(
               {views::Builder<views::Combobox>()
                    .CopyAddressTo(&lab_state_combobox_)
+                   .SetTooltipTextAndAccessibleName(l10n_util::GetStringUTF16(
+                       IDS_TOOLTIP_CHROMELABS_COMBOBOX))
                    .SetOwnedModel(std::make_unique<LabsComboboxModel>(
-                       feature_entry_, default_index))
+                       lab, feature_entry_, default_index))
                    .SetCallback(base::BindRepeating(combobox_callback, this))
 
                    .SetProperty(views::kFlexBehaviorKey,
@@ -114,6 +149,9 @@
                    .SetSizeToLargestLabel(false),
                views::Builder<views::MdTextButton>()
                    .CopyAddressTo(&feedback_button_)
+                   .SetTooltipText(l10n_util::GetStringFUTF16(
+                       IDS_TOOLTIP_CHROMELABS_FEEDBACK_BUTTON,
+                       lab.visible_name))
                    .SetCallback(base::BindRepeating(&ShowFeedbackPage, browser,
                                                     lab.feedback_category_name,
                                                     lab.visible_name))
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 5b56e44e..a990d7b 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -685,6 +685,9 @@
      IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_LABEL},
     {"spellCheckEnhancedDescription",
      IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_DESCRIPTION},
+    // Managed dialog strings:
+    {"languageManagedDialogTitle", IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE},
+    {"languageManagedDialogBody", IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY},
 #if !defined(OS_MAC)
     {"spellCheckDisabledReason",
      IDS_SETTING_LANGUAGES_SPELL_CHECK_DISABLED_REASON},
diff --git a/chrome/browser/web_applications/components/url_handler_prefs.cc b/chrome/browser/web_applications/components/url_handler_prefs.cc
index 64a6830..8e0cc53 100644
--- a/chrome/browser/web_applications/components/url_handler_prefs.cc
+++ b/chrome/browser/web_applications/components/url_handler_prefs.cc
@@ -102,7 +102,7 @@
 
     base::Optional<base::FilePath> profile_path =
         util::ValueToFilePath(handler.FindKey(kProfilePath));
-    if (!profile_path)
+    if (!profile_path || profile_path->empty())
       continue;
 
     if (origin_trimmed) {
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index 2919eb8..14d2a8e4 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -133,7 +133,8 @@
     }
     // We need "FileHandling" to use File Handling API to set launch directory.
     infos.at(SystemAppType::CAMERA).enabled_origin_trials =
-        OriginTrialsMap({{GetOrigin("chrome://camera-app"), {"FileHandling"}}});
+        OriginTrialsMap({{GetOrigin("chrome://camera-app"),
+                          {"FileHandling", "IdleDetection"}}});
     infos.at(SystemAppType::CAMERA).capture_navigations = true;
 
     // Minimum height +32 for top bar height.
diff --git a/chrome/common/media/cdm_manifest.cc b/chrome/common/media/cdm_manifest.cc
index aeede07..62a711a7 100644
--- a/chrome/common/media/cdm_manifest.cc
+++ b/chrome/common/media/cdm_manifest.cc
@@ -20,7 +20,6 @@
 #include "base/values.h"
 #include "base/version.h"
 #include "content/public/common/cdm_info.h"
-#include "extensions/common/manifest_constants.h"
 #include "media/base/content_decryption_module.h"
 #include "media/base/decrypt_config.h"
 #include "media/base/video_codecs.h"
@@ -46,6 +45,10 @@
 // delimited by commas. No trailing commas. For example, "1,2,4".
 const char kCdmValueDelimiter[] = ",";
 
+// This field in the manifest is parsed by component updater code (e.g.
+// `ComponentInstaller::FindPreinstallation()`) as well as in this file.
+const char kCdmVersion[] = "version";
+
 // The following entries are required.
 //  Interface versions are lists of integers (e.g. "1" or "1,2,4").
 //  All match the interface versions from content_decryption_module.h that the
@@ -258,11 +261,9 @@
 
 bool GetVersion(const base::Value& manifest, base::Version* version) {
   DCHECK(manifest.is_dict());
-  auto* version_string =
-      manifest.FindStringKey(extensions::manifest_keys::kVersion);
+  auto* version_string = manifest.FindStringKey(kCdmVersion);
   if (!version_string) {
-    DLOG(ERROR) << "CDM manifest missing "
-                << extensions::manifest_keys::kVersion;
+    DLOG(ERROR) << "CDM manifest missing " << kCdmVersion;
     return false;
   }
 
diff --git a/chrome/installer/PRESUBMIT.py b/chrome/installer/PRESUBMIT.py
new file mode 100644
index 0000000..6772ffa7
--- /dev/null
+++ b/chrome/installer/PRESUBMIT.py
@@ -0,0 +1,30 @@
+# Copyright 2021 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.
+
+PRESUBMIT_VERSION = '2.0.0'
+
+def CheckBreakingInstallerVersionBumpNeeded(input_api, output_api):
+  files = []
+  breaking_version_installer_updated = False
+
+  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
+    breaking_version_installer_updated |= (f.LocalPath() ==
+    'chrome/installer/setup/last_breaking_installer_version.cc')
+    if (f.LocalPath() == 'chrome/installer/mini_installer/chrome.release' or
+        f.LocalPath().startswith('chrome/test/mini_installer')):
+      files.append(f.LocalPath())
+
+    if files and not breaking_version_installer_updated:
+      return [output_api.PresubmitPromptWarning('''
+Update chrome/installer/setup/last_breaking_installer_version.cc if the changes
+found in the following files might break make downgrades not possible beyond
+this browser's version.''', items=files)]
+
+    if not files and breaking_version_installer_updated:
+      return [output_api.PresubmitPromptWarning('''
+No installer breaking changes detected but
+chrome/installer/setup/last_breaking_installer_version.cc was updated. Please
+update chrome/installer/presubmit.py if more files need to be watched for
+breaking installer changes.''')]
+    return []
diff --git a/chrome/installer/setup/BUILD.gn b/chrome/installer/setup/BUILD.gn
index d24feae..1a56c73 100644
--- a/chrome/installer/setup/BUILD.gn
+++ b/chrome/installer/setup/BUILD.gn
@@ -57,6 +57,8 @@
       "archive_patch_helper.cc",
       "archive_patch_helper.h",
       "brand_behaviors.h",
+      "downgrade_cleanup.cc",
+      "downgrade_cleanup.h",
       "install.cc",
       "install.h",
       "install_params.h",
@@ -68,6 +70,8 @@
       "installer_crash_reporting.h",
       "installer_state.cc",
       "installer_state.h",
+      "last_breaking_installer_version.cc",
+      "last_breaking_installer_version.h",
       "launch_chrome.cc",
       "launch_chrome.h",
       "modify_params.h",
@@ -124,6 +128,7 @@
   test("setup_unittests") {
     sources = [
       "archive_patch_helper_unittest.cc",
+      "downgrade_cleanup_unittest.cc",
       "install_unittest.cc",
       "install_worker_unittest.cc",
       "installer_state_unittest.cc",
diff --git a/chrome/installer/setup/downgrade_cleanup.cc b/chrome/installer/setup/downgrade_cleanup.cc
new file mode 100644
index 0000000..9db42c5
--- /dev/null
+++ b/chrome/installer/setup/downgrade_cleanup.cc
@@ -0,0 +1,204 @@
+// Copyright 2021 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/installer/setup/downgrade_cleanup.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "base/win/registry.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/installer/setup/installer_state.h"
+#include "chrome/installer/setup/setup_constants.h"
+#include "chrome/installer/util/callback_work_item.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "chrome/installer/util/install_util.h"
+#include "chrome/installer/util/util_constants.h"
+#include "chrome/installer/util/work_item_list.h"
+
+namespace {
+
+constexpr base::WStringPiece kCleanupOperation = L"cleanup";
+constexpr base::WStringPiece kRevertCleaunpOperation = L"revert";
+
+// Returns the last version of Chrome which introduced breaking changes to the
+// installer, or no value if Chrome is not installed or the version installed
+// predates support for this feature.
+base::Optional<base::Version> GetLastBreakingInstallerVersion(HKEY reg_root) {
+  base::win::RegKey key;
+  std::wstring last_breaking_installer_version;
+  if (key.Open(reg_root, install_static::GetClientStateKeyPath().c_str(),
+               KEY_QUERY_VALUE | KEY_WOW64_32KEY) != ERROR_SUCCESS ||
+      key.ReadValue(google_update::kRegCleanInstallRequiredForVersionBelowField,
+                    &last_breaking_installer_version) != ERROR_SUCCESS ||
+      last_breaking_installer_version.empty()) {
+    return base::nullopt;
+  }
+  base::Version version(base::WideToASCII(last_breaking_installer_version));
+  if (!version.IsValid())
+    return base::nullopt;
+  return version;
+}
+// Formats `cmd_line_with_placeholders` by replacing the placeholders with
+// `version` and `operation`. Returns an empty string if some placeholder
+// replacements are missing.
+std::wstring GetCleanupCommandLine(
+    const std::wstring& cmd_line_with_placeholders,
+    const base::Version& version,
+    base::WStringPiece operation) {
+  DCHECK(version.IsValid());
+  DCHECK(!cmd_line_with_placeholders.empty());
+  DCHECK(operation == kCleanupOperation ||
+         operation == kRevertCleaunpOperation);
+  std::vector<size_t> offsets;
+  std::vector<std::u16string> replacements{
+      base::ASCIIToUTF16(version.GetString()), base::AsString16(operation)};
+  auto cmd = base::ReplaceStringPlaceholders(
+      base::AsString16(cmd_line_with_placeholders), replacements, &offsets);
+  // The `offsets` size and `replacements` size should be equal. If they are
+  // not, no command should be returned to avoid running an invalid command
+  // line.
+  if (offsets.size() != replacements.size())
+    cmd.clear();
+  return base::AsWString(std::move(cmd));
+}
+
+// Returns true if after a downgrade, `cmd` was run successfully to cleanup
+// after a downgrade crossing a breaking installer version. `cmd` is expected to
+// be a correctly formatted command line that calls the installer of the version
+// we downgraded from with the right version and 'cleanup' as operation
+// parameter.
+bool LaunchCleanupForBreakingDowngradeProcess(
+    const std::wstring& cmd,
+    const CallbackWorkItem& work_item) {
+  DCHECK(!cmd.empty());
+  VLOG(1) << "Launching downgrade cleanup process: " << cmd;
+  base::Process process = base::LaunchProcess(cmd, base::LaunchOptions());
+  if (!process.IsValid()) {
+    PLOG(ERROR) << "Failed to launch child process \"" << cmd << "\"";
+    return false;
+  }
+  int exit_code = installer::DOWNGRADE_CLEANUP_SUCCESS;
+  process.WaitForExit(&exit_code);
+
+  if (exit_code == installer::DOWNGRADE_CLEANUP_SUCCESS) {
+    VLOG(1) << "Downgrade cleanup process succeeded";
+    return true;
+  }
+  LOG(ERROR) << "Downgrade cleanup process \"" << cmd
+             << "\" failed with exit code " << exit_code;
+  return false;
+}
+
+// Runs `cmd` to revert any cleanup done after a downgrade crossing a breaking
+// installer version in the context of a CallbackWorkItem. `cmd` is expected to
+// be a correctly formatted command line that calls the installer of the version
+// we downgraded from with the right version and 'revert' as operation
+// parameter.
+void LaunchUndoCleanupForBreakingDowngradeProcess(
+    const std::wstring& cmd,
+    const CallbackWorkItem& work_item) {
+  DCHECK(!cmd.empty());
+  VLOG(1) << "Launching downgrade cleanup undo process: " << cmd;
+  base::Process process = base::LaunchProcess(cmd, base::LaunchOptions());
+  if (!process.IsValid()) {
+    PLOG(ERROR) << "Failed to launch child process \"" << cmd << "\"";
+    return;
+  }
+
+  int exit_code = installer::UNDO_DOWNGRADE_CLEANUP_SUCCESS;
+  process.WaitForExit(&exit_code);
+
+  if (exit_code == installer::UNDO_DOWNGRADE_CLEANUP_SUCCESS) {
+    VLOG(1) << "Downgrade cleanup undo process succeeded";
+    return;
+  }
+
+  LOG(ERROR) << "Downgrade cleanup undo process \"" << cmd
+             << "\" failed with exit code " << exit_code;
+}
+
+}  // namespace
+
+namespace installer {
+
+InstallStatus ProcessCleanupForDowngrade(const base::Version& version,
+                                         bool revert) {
+  if (revert) {
+    return version.IsValid() ? UNDO_DOWNGRADE_CLEANUP_SUCCESS
+                             : UNDO_DOWNGRADE_CLEANUP_FAILED;
+  }
+  return version.IsValid() ? DOWNGRADE_CLEANUP_SUCCESS
+                           : DOWNGRADE_CLEANUP_FAILED;
+}
+
+std::wstring GetDowngradeCleanupCommandWithPlaceholders(
+    const base::FilePath& installer_path,
+    const InstallerState& installer_state) {
+  base::CommandLine downgrade_cleanup_cmd(installer_path);
+  downgrade_cleanup_cmd.AppendSwitchNative(
+      switches::kCleanupForDowngradeVersion, L"$1");
+  downgrade_cleanup_cmd.AppendSwitchNative(
+      switches::kCleanupForDowngradeOperation, L"$2");
+  InstallUtil::AppendModeAndChannelSwitches(&downgrade_cleanup_cmd);
+  if (installer_state.system_install())
+    downgrade_cleanup_cmd.AppendSwitch(switches::kSystemLevel);
+  if (installer_state.verbose_logging())
+    downgrade_cleanup_cmd.AppendSwitch(switches::kVerboseLogging);
+  return downgrade_cleanup_cmd.GetCommandLineString();
+}
+
+bool AddDowngradeCleanupItems(const base::Version& new_version,
+                              WorkItemList* list) {
+  DCHECK(new_version.IsValid());
+  HKEY reg_root = install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE
+                                                    : HKEY_CURRENT_USER;
+  if (GetLastBreakingInstallerVersion(reg_root) <= new_version)
+    return false;
+
+  std::wstring dowgrade_cleanup_cmd;
+  base::win::RegKey(reg_root, install_static::GetClientStateKeyPath().c_str(),
+                    KEY_QUERY_VALUE | KEY_WOW64_32KEY)
+      .ReadValue(google_update::kRegDowngradeCleanupCommandField,
+                 &dowgrade_cleanup_cmd);
+  if (dowgrade_cleanup_cmd.empty())
+    return false;
+
+  auto cleanup_cmd = GetCleanupCommandLine(dowgrade_cleanup_cmd, new_version,
+                                           kCleanupOperation);
+  if (cleanup_cmd.empty()) {
+    LOG(ERROR) << "Unable to format the downgrade cleanup command \""
+               << dowgrade_cleanup_cmd << "\"";
+    return false;
+  }
+
+  auto revert_cmd = GetCleanupCommandLine(dowgrade_cleanup_cmd, new_version,
+                                          kRevertCleaunpOperation);
+  if (revert_cmd.empty()) {
+    LOG(ERROR) << "Unable to format the revert downgrade cleanup command \""
+               << dowgrade_cleanup_cmd << "\"";
+    return false;
+  }
+
+  VLOG(1) << "Setting up cleanup for downgrade to version " << new_version;
+
+  list->AddCallbackWorkItem(
+      base::BindOnce(&LaunchCleanupForBreakingDowngradeProcess,
+                     std::move(cleanup_cmd)),
+      base::BindOnce(&LaunchUndoCleanupForBreakingDowngradeProcess,
+                     std::move(revert_cmd)));
+  return true;
+}
+
+}  // namespace installer
diff --git a/chrome/installer/setup/downgrade_cleanup.h b/chrome/installer/setup/downgrade_cleanup.h
new file mode 100644
index 0000000..c547cc1
--- /dev/null
+++ b/chrome/installer/setup/downgrade_cleanup.h
@@ -0,0 +1,48 @@
+// Copyright 2021 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_INSTALLER_SETUP_DOWNGRADE_CLEANUP_H_
+#define CHROME_INSTALLER_SETUP_DOWNGRADE_CLEANUP_H_
+
+#include <string>
+
+#include "chrome/installer/util/util_constants.h"
+
+namespace base {
+class FilePath;
+class Version;
+}  // namespace base
+
+class WorkItemList;
+
+namespace installer {
+
+class InstallerState;
+
+// Cleans stale data after a downgrade to `version` if `revert` is false. If
+// `revert` is true, revert a previous attempt to cleanup after a downgrade to
+// `version`.
+InstallStatus ProcessCleanupForDowngrade(const base::Version& version,
+                                         bool revert);
+
+// Returns the command line to cleanup after a downgrade. This command line has
+// two placeholders, the first one for the version to which we are downgrading
+// and the second one to decide if we are to do the cleanup or revert a previous
+// cleanup. The second placeholder accepts "cleanup" or "revert" as value. The
+// command will fail to run if either the version or the operation type is empty
+// or invalid.
+std::wstring GetDowngradeCleanupCommandWithPlaceholders(
+    const base::FilePath& installer_path,
+    const InstallerState& installer_state);
+
+// Adds the work items to cleanup stale data in case the installation of
+// `new_version` results in a downgrade that crosses a breaking installer
+// version. If no cleanup is necessary, this does nothing. This returns `true`
+// if items are actually added to `list` and `false` otherwise.
+bool AddDowngradeCleanupItems(const base::Version& new_version,
+                              WorkItemList* list);
+
+}  // namespace installer
+
+#endif  // CHROME_INSTALLER_SETUP_DOWNGRADE_CLEANUP_H_
diff --git a/chrome/installer/setup/downgrade_cleanup_unittest.cc b/chrome/installer/setup/downgrade_cleanup_unittest.cc
new file mode 100644
index 0000000..9fb951f9
--- /dev/null
+++ b/chrome/installer/setup/downgrade_cleanup_unittest.cc
@@ -0,0 +1,211 @@
+// Copyright 2021 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/installer/setup/downgrade_cleanup.h"
+
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/version.h"
+#include "base/win/registry.h"
+#include "base/win/windows_types.h"
+#include "chrome/install_static/install_util.h"
+#include "chrome/install_static/test/scoped_install_details.h"
+#include "chrome/installer/setup/setup_constants.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "chrome/installer/util/work_item.h"
+#include "chrome/installer/util/work_item_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace installer {
+
+namespace {
+
+constexpr wchar_t kVersion[] = L"80.0.0.0";
+constexpr wchar_t kLastBreakingVersion[] = L"81.0.0.0";
+constexpr char kDowngradeCleanupSuccessMain[] = "DowngradeCleanupSuccessMain";
+constexpr wchar_t kDummyKey1[] = L"dummy1";
+constexpr wchar_t kDummyKey2[] = L"dummy2";
+constexpr int64_t kDummyValue1 = 2019;
+constexpr int64_t kDummyValue2 = 2020;
+
+}  // namespace
+
+// Test fixture to verify that `AddDowngradeCleanupItems` launches the right
+// processes with the required placeholder replacements. This mimics the command
+// line returned by `GetDowngradeCleanupCommandWithPlaceholders`, but the
+// process launched is a test process in which the command line arguments are
+// verified
+class DowngradeCleanupTest : public base::MultiProcessTest {
+ protected:
+  DowngradeCleanupTest() = default;
+
+  void SetUp() override {
+    ASSERT_NO_FATAL_FAILURE(
+        registry_override_manager_.OverrideRegistry(reg_root()));
+  }
+
+  // Returns a commande line with the placeholders required by
+  // `GetDowngradeCleanupCommandWithPlaceholders`.
+  base::CommandLine MakeCmdLine(const std::string& procname) override {
+    base::CommandLine command_line =
+        base::MultiProcessTest::MakeCmdLine(procname);
+    command_line.AppendSwitchNative(switches::kCleanupForDowngradeVersion,
+                                    L"$1");
+    command_line.AppendSwitchNative(switches::kCleanupForDowngradeOperation,
+                                    L"$2");
+    return command_line;
+  }
+
+  const wchar_t* client_state_key() const { return client_state_key_.c_str(); }
+
+  // Adds a dummy work item before and after the cleanup work items. If
+  // `expect_cleanup_items_added` is true, `AddDowngradeCleanupItems` must have
+  // added some work items in `list`, otherwise it should not have added any
+  // work items.
+  void AddCleanupWorkItems(WorkItemList* list,
+                           bool expect_cleanup_items_added) {
+    list->AddSetRegValueWorkItem(reg_root(), client_state_key(),
+                                 KEY_WOW64_32KEY, kDummyKey1, kDummyValue1,
+                                 false);
+    ASSERT_EQ(AddDowngradeCleanupItems(
+                  base::Version(base::WideToASCII(kVersion)), list),
+              expect_cleanup_items_added);
+    list->AddSetRegValueWorkItem(reg_root(), client_state_key(),
+                                 KEY_WOW64_32KEY, kDummyKey2, kDummyValue2,
+                                 false);
+  }
+
+  void ExpectPreCleanupWorkItems(bool applied) {
+    int64_t value = 0;
+    base::win::RegKey(reg_root(), client_state_key(),
+                      KEY_READ | KEY_WOW64_32KEY)
+        .ReadInt64(kDummyKey1, &value);
+    EXPECT_EQ(value, (applied ? kDummyValue1 : 0));
+  }
+
+  void ExpectPostCleanupWorkItems(bool applied) {
+    int64_t expected_value = 0;
+    base::win::RegKey(reg_root(), client_state_key(),
+                      KEY_READ | KEY_WOW64_32KEY)
+        .ReadInt64(kDummyKey2, &expected_value);
+    EXPECT_EQ(expected_value, applied ? kDummyValue2 : 0);
+  }
+
+  static HKEY reg_root() {
+    return install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE
+                                             : HKEY_CURRENT_USER;
+  }
+
+ private:
+  install_static::ScopedInstallDetails install_details_{true};
+  registry_util::RegistryOverrideManager registry_override_manager_;
+  const std::wstring client_state_key_ =
+      install_static::GetClientStateKeyPath();
+};
+
+MULTIPROCESS_TEST_MAIN(DowngradeCleanupSuccessMain) {
+  install_static::ScopedInstallDetails install_details(/*system_level=*/true);
+  base::CommandLine* const command_line =
+      base::CommandLine::ForCurrentProcess();
+
+  auto version =
+      command_line->GetSwitchValueNative(switches::kCleanupForDowngradeVersion);
+  auto operation = command_line->GetSwitchValueNative(
+      switches::kCleanupForDowngradeOperation);
+  auto should_fail = command_line->HasSwitch("fail");
+
+  installer::InstallStatus result_code = DOWNGRADE_CLEANUP_UNKNOWN_OPERATION;
+  EXPECT_EQ(version, kVersion);
+  if (operation == L"cleanup") {
+    result_code = should_fail ? installer::DOWNGRADE_CLEANUP_FAILED
+                              : installer::DOWNGRADE_CLEANUP_SUCCESS;
+  } else if (operation == L"revert") {
+    result_code = installer::UNDO_DOWNGRADE_CLEANUP_SUCCESS;
+  } else {
+    NOTREACHED();
+  }
+
+  return ::testing::Test::HasFailure() ? -1 : result_code;
+}
+
+TEST_F(DowngradeCleanupTest, SuccessfulCleanup) {
+  std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
+
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(google_update::kRegDowngradeCleanupCommandField,
+                            MakeCmdLine(kDowngradeCleanupSuccessMain)
+                                .GetCommandLineString()
+                                .c_str()),
+            ERROR_SUCCESS);
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(
+                    google_update::kRegCleanInstallRequiredForVersionBelowField,
+                    kLastBreakingVersion),
+            ERROR_SUCCESS);
+  ASSERT_NO_FATAL_FAILURE(
+      AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/true));
+  ASSERT_TRUE(list->Do());
+  ExpectPreCleanupWorkItems(/*applied=*/true);
+  ExpectPostCleanupWorkItems(/*applied=*/true);
+
+  list->Rollback();
+  ExpectPreCleanupWorkItems(/*applied=*/false);
+  ExpectPostCleanupWorkItems(/*applied=*/false);
+}
+
+TEST_F(DowngradeCleanupTest, FailedCleanup) {
+  std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
+  auto cmd = MakeCmdLine(kDowngradeCleanupSuccessMain);
+  cmd.AppendSwitch("fail");
+
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(google_update::kRegDowngradeCleanupCommandField,
+                            cmd.GetCommandLineString().c_str()),
+            ERROR_SUCCESS);
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(
+                    google_update::kRegCleanInstallRequiredForVersionBelowField,
+                    kLastBreakingVersion),
+            ERROR_SUCCESS);
+  ASSERT_NO_FATAL_FAILURE(
+      AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/true));
+  EXPECT_FALSE(list->Do());
+  ExpectPreCleanupWorkItems(/*applied=*/true);
+  ExpectPostCleanupWorkItems(/*applied=*/false);
+
+  list->Rollback();
+  ExpectPreCleanupWorkItems(/*applied=*/false);
+  ExpectPostCleanupWorkItems(/*applied=*/false);
+}
+
+TEST_F(DowngradeCleanupTest, MissingArguments) {
+  std::unique_ptr<WorkItemList> list;
+  list.reset(WorkItem::CreateWorkItemList());
+  auto cmd = MakeCmdLine(kDowngradeCleanupSuccessMain);
+  // Currently only 2 placeholders are supported.
+  cmd.AppendSwitchNative("missing", L"$3");
+
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(google_update::kRegDowngradeCleanupCommandField,
+                            cmd.GetCommandLineString().c_str()),
+            ERROR_SUCCESS);
+  ASSERT_EQ(base::win::RegKey(reg_root(), client_state_key(),
+                              KEY_SET_VALUE | KEY_WOW64_32KEY)
+                .WriteValue(
+                    google_update::kRegCleanInstallRequiredForVersionBelowField,
+                    kLastBreakingVersion),
+            ERROR_SUCCESS);
+  ASSERT_NO_FATAL_FAILURE(
+      AddCleanupWorkItems(list.get(), /*expect_cleanup_items_added=*/false));
+}
+
+}  // namespace installer
diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc
index e349b161..b040408 100644
--- a/chrome/installer/setup/install_worker.cc
+++ b/chrome/installer/setup/install_worker.cc
@@ -39,8 +39,10 @@
 #include "chrome/install_static/install_details.h"
 #include "chrome/install_static/install_modes.h"
 #include "chrome/install_static/install_util.h"
+#include "chrome/installer/setup/downgrade_cleanup.h"
 #include "chrome/installer/setup/install_params.h"
 #include "chrome/installer/setup/installer_state.h"
+#include "chrome/installer/setup/last_breaking_installer_version.h"
 #include "chrome/installer/setup/setup_constants.h"
 #include "chrome/installer/setup/setup_util.h"
 #include "chrome/installer/setup/update_active_setup_version_work_item.h"
@@ -652,6 +654,10 @@
   base::FilePath new_chrome_exe(target_path.Append(kChromeNewExe));
   const std::wstring clients_key(install_static::GetClientsKeyPath());
 
+  base::FilePath installer_path(
+      installer_state.GetInstallerDirectory(new_version)
+          .Append(setup_path.BaseName()));
+
   // Append work items that will only be executed if this was an in-use update.
   // We update the 'opv' value with the current version that is active,
   // the 'cpv' value with the critical update version (if present), and the
@@ -666,9 +672,6 @@
     // version considered critical relative to the version being updated.
     base::Version critical_version(
         installer_state.DetermineCriticalVersion(current_version, new_version));
-    base::FilePath installer_path(
-        installer_state.GetInstallerDirectory(new_version)
-            .Append(setup_path.BaseName()));
 
     if (current_version.IsValid()) {
       in_use_update_work_items->AddSetRegValueWorkItem(
@@ -718,6 +721,8 @@
     // with it so that the browser knows which channel to use, otherwise delete
     // whatever value that key holds.
     AddChannelWorkItems(root, clients_key, regular_update_work_items.get());
+    AddFinalizeUpdateWorkItems(new_version, installer_state, installer_path,
+                               regular_update_work_items.get());
 
     // Since this was not an in-use-update, delete 'opv', 'cpv',
     // and 'cmd' keys.
@@ -1058,4 +1063,31 @@
   }
 }
 
+void AddFinalizeUpdateWorkItems(const base::Version& new_version,
+                                const InstallerState& installer_state,
+                                const base::FilePath& setup_path,
+                                WorkItemList* list) {
+  // Cleanup for breaking downgrade first in the post install to avoid
+  // overwriting any of the following post-install tasks.
+  AddDowngradeCleanupItems(new_version, list);
+
+  const std::wstring client_state_key = install_static::GetClientStateKeyPath();
+
+  // Adds the command that needs to be used in order to cleanup any breaking
+  // changes the installer of this version may have added.
+  list->AddSetRegValueWorkItem(
+      installer_state.root_key(), client_state_key, KEY_WOW64_32KEY,
+      google_update::kRegDowngradeCleanupCommandField,
+      GetDowngradeCleanupCommandWithPlaceholders(setup_path, installer_state),
+      true);
+
+  // Write the latest installer's breaking version so that future downgrades
+  // know if they need to do a clean install. This isn't done for in-use since
+  // it is done at the the executable's rename.
+  list->AddSetRegValueWorkItem(
+      installer_state.root_key(), client_state_key, KEY_WOW64_32KEY,
+      google_update::kRegCleanInstallRequiredForVersionBelowField,
+      kLastBreakingInstallerVersion, true);
+}
+
 }  // namespace installer
diff --git a/chrome/installer/setup/install_worker.h b/chrome/installer/setup/install_worker.h
index e65b180..8760120c1 100644
--- a/chrome/installer/setup/install_worker.h
+++ b/chrome/installer/setup/install_worker.h
@@ -106,6 +106,14 @@
                          const std::wstring& clients_key,
                          WorkItemList* list);
 
+// Adds work items to be done when finalizing an update. This happens both
+// after the executables get renamed for an in-use update or as the last steps
+// for a regular update.
+void AddFinalizeUpdateWorkItems(const base::Version& new_version,
+                                const InstallerState& installer_state,
+                                const base::FilePath& setup_path,
+                                WorkItemList* list);
+
 }  // namespace installer
 
 #endif  // CHROME_INSTALLER_SETUP_INSTALL_WORKER_H_
diff --git a/chrome/installer/setup/last_breaking_installer_version.cc b/chrome/installer/setup/last_breaking_installer_version.cc
new file mode 100644
index 0000000..262b62a
--- /dev/null
+++ b/chrome/installer/setup/last_breaking_installer_version.cc
@@ -0,0 +1,22 @@
+// Copyright 2021 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/installer/setup/last_breaking_installer_version.h"
+
+namespace installer {
+
+// Document any breaking change when updating `kLastBreakingInstallerVersion`
+// with the breaking change and the reason why it is a breaking change.
+// When updating the documentation, keep a history of maximum 3 milestones prior
+// the current version because downgrades are supported for 3 milestones.
+//
+// Breaking changes from 85.0.4169.0:
+//  Change: Default installation directory for fresh 64 bits browsers moved from
+//    base::DIR_PROGRAM_FILESX86 to DIR_PROGRAM_FILES.
+//  Reason for being breaking: Downgrading to previous version will result in
+//    stale data in DIR_PROGRAM_FILES since the previous installers do not know
+//    if there is Chrome data at this location.
+const wchar_t kLastBreakingInstallerVersion[] = L"85.0.4169.0";
+
+}  // namespace installer
diff --git a/chrome/installer/setup/last_breaking_installer_version.h b/chrome/installer/setup/last_breaking_installer_version.h
new file mode 100644
index 0000000..5e5d304
--- /dev/null
+++ b/chrome/installer/setup/last_breaking_installer_version.h
@@ -0,0 +1,14 @@
+// Copyright 2021 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_INSTALLER_SETUP_LAST_BREAKING_INSTALLER_VERSION_H_
+#define CHROME_INSTALLER_SETUP_LAST_BREAKING_INSTALLER_VERSION_H_
+
+namespace installer {
+
+extern const wchar_t kLastBreakingInstallerVersion[];
+
+}  // namespace installer
+
+#endif  // CHROME_INSTALLER_SETUP_LAST_BREAKING_INSTALLER_VERSION_H_
diff --git a/chrome/installer/setup/setup_constants.cc b/chrome/installer/setup/setup_constants.cc
index 4be3d16b..3f0f434 100644
--- a/chrome/installer/setup/setup_constants.cc
+++ b/chrome/installer/setup/setup_constants.cc
@@ -41,6 +41,16 @@
 // Run setup.exe to conduct a post-update experiment.
 const char kUserExperiment[] = "user-experiment";
 
+// Sets the operation to do for the downgrade cleanup. Only the values "revert"
+// and "cleanup" are accepted. If the operation is "cleanup", cleans up the
+// necessary data, if the operation is "revert", reverts any cleanup previously
+// done. Any other value will have no effect and will generate an error.
+const char kCleanupForDowngradeOperation[] = "cleanup-for-downgrade-operation";
+
+// Indicates the version to which the browser is being downgraded. All state
+// written by versions newer than that indicated will be cleaned.
+const char kCleanupForDowngradeVersion[] = "cleanup-for-downgrade-version";
+
 }  // namespace switches
 
 }  // namespace installer
diff --git a/chrome/installer/setup/setup_constants.h b/chrome/installer/setup/setup_constants.h
index bb05bf5..45c86843 100644
--- a/chrome/installer/setup/setup_constants.h
+++ b/chrome/installer/setup/setup_constants.h
@@ -42,6 +42,9 @@
 extern const char kSetDisplayVersionValue[];
 extern const char kUserExperiment[];
 
+extern const char kCleanupForDowngradeOperation[];
+extern const char kCleanupForDowngradeVersion[];
+
 }  // namespace switches
 
 }  // namespace installer
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index f34924a..49abd2f 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -57,6 +57,7 @@
 #include "chrome/installer/setup/archive_patch_helper.h"
 #include "chrome/installer/setup/brand_behaviors.h"
 #include "chrome/installer/setup/buildflags.h"
+#include "chrome/installer/setup/downgrade_cleanup.h"
 #include "chrome/installer/setup/install.h"
 #include "chrome/installer/setup/install_params.h"
 #include "chrome/installer/setup/install_worker.h"
@@ -452,9 +453,12 @@
                                     temp_path.path(), WorkItem::ALWAYS_MOVE);
   install_list->AddDeleteTreeWorkItem(chrome_proxy_new_exe, temp_path.path());
 
+  AddFinalizeUpdateWorkItems(base::Version(chrome::kChromeVersion),
+                             *installer_state, setup_exe, install_list.get());
+
   // Add work items to delete Chrome's "opv", "cpv", and "cmd" values.
   // TODO(grt): Clean this up; https://crbug.com/577816.
-  HKEY reg_root = installer_state->root_key();
+  const HKEY reg_root = installer_state->root_key();
   const std::wstring clients_key = install_static::GetClientsKeyPath();
 
   install_list->AddDeleteRegValueWorkItem(reg_root, clients_key,
@@ -936,6 +940,21 @@
           RenameChromeExecutables(setup_exe, *original_state, installer_state);
     }
   } else if (cmd_line.HasSwitch(
+                 installer::switches::kCleanupForDowngradeVersion)) {
+    // The version being downgraded to.
+    std::string new_version = cmd_line.GetSwitchValueASCII(
+        installer::switches::kCleanupForDowngradeVersion);
+    std::wstring operation = cmd_line.GetSwitchValueNative(
+        installer::switches::kCleanupForDowngradeOperation);
+    if (operation == L"cleanup" || operation == L"revert") {
+      *exit_code = installer::ProcessCleanupForDowngrade(
+          base::Version(new_version), /*revert=*/operation == L"revert");
+    } else {
+      LOG(ERROR) << "Ignoring \"" << cmd_line.GetCommandLineString()
+                 << "\" because of invalid \"operation\" argument.";
+      *exit_code = installer::DOWNGRADE_CLEANUP_UNKNOWN_OPERATION;
+    }
+  } else if (cmd_line.HasSwitch(
                  installer::switches::kRemoveChromeRegistration)) {
     // This is almost reverse of --register-chrome-browser option above.
     // Here we delete Chrome browser registration. This option should only
diff --git a/chrome/installer/util/delete_old_versions.cc b/chrome/installer/util/delete_old_versions.cc
index 341e9c2..9c39b9a 100644
--- a/chrome/installer/util/delete_old_versions.cc
+++ b/chrome/installer/util/delete_old_versions.cc
@@ -40,6 +40,7 @@
 // directories named after the version of chrome.exe or new_chrome.exe are
 // excluded.
 DirectorySet GetOldVersionDirectories(const base::FilePath& install_dir) {
+  // TODO(crbug/1182976): Delete old version directory from all known locations.
   const base::FilePath new_chrome_exe_version_dir_name =
       GetExecutableVersionDirName(install_dir.Append(kChromeNewExe));
   const base::FilePath chrome_exe_version_dir_name =
diff --git a/chrome/installer/util/google_update_constants.cc b/chrome/installer/util/google_update_constants.cc
index 1e5ee12e..3f25910 100644
--- a/chrome/installer/util/google_update_constants.cc
+++ b/chrome/installer/util/google_update_constants.cc
@@ -31,10 +31,13 @@
 const wchar_t kRegCFOptOutCmdField[] = L"CFOptOutCmd";
 const wchar_t kRegCFTempOptOutCmdField[] = L"CFTempOptOutCmd";
 const wchar_t kRegChannelField[] = L"channel";
+const wchar_t kRegCleanInstallRequiredForVersionBelowField[] =
+    L"CleanInstallRequiredForVersionBelow";
 const wchar_t kRegClientField[] = L"client";
 const wchar_t kRegCommandLineField[] = L"CommandLine";
 const wchar_t kRegCriticalVersionField[] = L"cpv";
 const wchar_t kRegDidRunField[] = L"dr";
+const wchar_t kRegDowngradeCleanupCommandField[] = L"DowngradeCleanupCommand";
 const wchar_t kRegEulaAceptedField[] = L"eulaaccepted";
 const wchar_t kRegGoogleUpdateVersion[] = L"version";
 const wchar_t kRegInstallerProgress[] = L"InstallerProgress";
diff --git a/chrome/installer/util/google_update_constants.h b/chrome/installer/util/google_update_constants.h
index 20cb281..eb64180 100644
--- a/chrome/installer/util/google_update_constants.h
+++ b/chrome/installer/util/google_update_constants.h
@@ -47,10 +47,12 @@
 extern const wchar_t kRegCFOptOutCmdField[];
 extern const wchar_t kRegCFTempOptOutCmdField[];
 extern const wchar_t kRegChannelField[];
+extern const wchar_t kRegCleanInstallRequiredForVersionBelowField[];
 extern const wchar_t kRegClientField[];
 extern const wchar_t kRegCommandLineField[];
 extern const wchar_t kRegCriticalVersionField[];
 extern const wchar_t kRegDidRunField[];
+extern const wchar_t kRegDowngradeCleanupCommandField[];
 extern const wchar_t kRegEulaAceptedField[];
 extern const wchar_t kRegGoogleUpdateVersion[];
 extern const wchar_t kRegInstallerProgress[];
diff --git a/chrome/installer/util/set_reg_value_work_item.cc b/chrome/installer/util/set_reg_value_work_item.cc
index 4a0570f..6817551 100644
--- a/chrome/installer/util/set_reg_value_work_item.cc
+++ b/chrome/installer/util/set_reg_value_work_item.cc
@@ -193,6 +193,18 @@
     return false;
   }
 
+  if (VLOG_IS_ON(1)) {
+    if (type_ == REG_SZ) {
+      std::wstring value_str;
+      BinaryDataToString(value_, &value_str);
+      VLOG(1) << "Successfully wrote value " << value_str << " into "
+              << key_path_;
+
+    } else {
+      VLOG(1) << "Successfully wrote into " << key_path_;
+    }
+  }
+
   status_ = previous_type_ ? VALUE_OVERWRITTEN : NEW_VALUE_CREATED;
   return true;
 }
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index 4296136..dec1a69 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -112,8 +112,13 @@
                                // registry.
   STORE_DMTOKEN_SUCCESS = 65,  // Writing the specified DMToken to the registry
                                // succeeded.
-  MAX_INSTALL_STATUS = 66,     // When adding a new result, bump this and update
-                               // the InstallStatus enum in histograms.xml.
+  DOWNGRADE_CLEANUP_FAILED = 66,
+  DOWNGRADE_CLEANUP_SUCCESS = 67,
+  UNDO_DOWNGRADE_CLEANUP_FAILED = 68,
+  UNDO_DOWNGRADE_CLEANUP_SUCCESS = 69,
+  DOWNGRADE_CLEANUP_UNKNOWN_OPERATION = 70,
+  MAX_INSTALL_STATUS = 71,  // When adding a new result, bump this and update
+                            // the InstallStatus enum in histograms.xml.
 };
 
 // The type of an update archive.
diff --git a/chrome/renderer/media/chrome_speech_recognition_client.cc b/chrome/renderer/media/chrome_speech_recognition_client.cc
index 0a64463..cb356af 100644
--- a/chrome/renderer/media/chrome_speech_recognition_client.cc
+++ b/chrome/renderer/media/chrome_speech_recognition_client.cc
@@ -117,8 +117,8 @@
     bool is_speech_recognition_available) {
   if (is_speech_recognition_available) {
     initialize_callback_.Run();
-  } else {
-    Reset();
+  } else if (reset_callback_) {
+    reset_callback_.Run();
   }
 }
 
@@ -150,6 +150,10 @@
                               is_website_blocked_);
   }
 
+  // Bind the call to Reset() to the Media thread.
+  reset_callback_ = media::BindToCurrentLoop(base::BindRepeating(
+      &ChromeSpeechRecognitionClient::Reset, weak_factory_.GetWeakPtr()));
+
   speech_recognition_context_.set_disconnect_handler(media::BindToCurrentLoop(
       base::BindOnce(&ChromeSpeechRecognitionClient::OnRecognizerDisconnected,
                      weak_factory_.GetWeakPtr())));
diff --git a/chrome/renderer/media/chrome_speech_recognition_client.h b/chrome/renderer/media/chrome_speech_recognition_client.h
index 76d4df7..373b444 100644
--- a/chrome/renderer/media/chrome_speech_recognition_client.h
+++ b/chrome/renderer/media/chrome_speech_recognition_client.h
@@ -94,6 +94,8 @@
 
   media::SpeechRecognitionClient::OnReadyCallback on_ready_callback_;
 
+  base::RepeatingClosure reset_callback_;
+
   // Sends audio to the speech recognition thread on the renderer thread.
   SendAudioToSpeechRecognitionServiceCallback send_audio_callback_;
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 7ee32ad..a182b84 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5637,15 +5637,20 @@
     } else {
       sources += [
         "../browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc",
-        "../browser/extensions/api/messaging/native_message_process_host_unittest.cc",
         "../browser/extensions/api/messaging/native_messaging_host_manifest_unittest.cc",
-        "../browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc",
         "../browser/extensions/api/messaging/native_messaging_policy_handler_unittest.cc",
       ]
 
       deps += [ "//components/enterprise:test_support" ]
     }
+    if (!is_chromeos_ash && !is_chromeos_lacros) {
+      sources += [
+        "../browser/extensions/api/messaging/native_message_process_host_unittest.cc",
+        "../browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc",
+      ]
+    }
   }
+
   if (use_aura) {
     deps += [
       "//ui/aura:test_support",
diff --git a/chrome/test/data/extensions/api_test/apitest/manifest.json b/chrome/test/data/extensions/api_test/apitest/manifest.json
index aad2e1d..44e9bd28 100644
--- a/chrome/test/data/extensions/api_test/apitest/manifest.json
+++ b/chrome/test/data/extensions/api_test/apitest/manifest.json
@@ -4,6 +4,7 @@
   "manifest_version": 2,
   "description": "Tests for the extension api tests api.",
   "background": {
-    "scripts": ["background.js"]
+    "scripts": ["background.js"],
+    "persistent": true
   }
 }
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 849320593..e84b9c26f 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -8384,7 +8384,7 @@
       }
     ]
   },
-  "BrowserLabsEnabled":{
+  "BrowserLabsEnabled": {
     "os": ["win", "linux", "mac"],
     "policy_pref_mapping_tests":[
       {
@@ -8533,5 +8533,22 @@
         }
       }
     ]
+  },
+  "ForcedLanguages": {
+    "os": ["win", "linux", "mac"],
+    "can_be_recommended": false,
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {
+          "ForcedLanguages": ["en-US", "fr-FR"]
+        },
+        "prefs": {
+          "intl.forced_languages": {
+            "value": ["en-US", "fr-FR"],
+            "location": "user_profile"
+          }
+        }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_v3_browsertest.js b/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_v3_browsertest.js
index 25ddec41..8335afe 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_v3_browsertest.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cr_components_chromeos_v3_browsertest.js
@@ -75,7 +75,6 @@
       return {
         enabled: [
           'chromeos::features::kConnectivityDiagnosticsWebUi',
-          'chromeos::features::kOsSettingsPolymer3',
           'chromeos::features::kUpdatedCellularActivationUi',
         ],
       };
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 49da500..7e2b775 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -27,7 +27,6 @@
     return {
       enabled: [
         'chromeos::features::kEnableHostnameSetting',
-        'chromeos::features::kOsSettingsPolymer3',
         'chromeos::features::kUpdatedCellularActivationUi',
         'features::kCrostini',
       ],
diff --git a/chrome/test/data/webui/settings/fake_language_settings_private.js b/chrome/test/data/webui/settings/fake_language_settings_private.js
index 0eabd60..c0907e7 100644
--- a/chrome/test/data/webui/settings/fake_language_settings_private.js
+++ b/chrome/test/data/webui/settings/fake_language_settings_private.js
@@ -475,6 +475,11 @@
         value: 'en-US,sw',
       },
       {
+        key: 'intl.forced_languages',
+        type: chrome.settingsPrivate.PrefType.LIST,
+        value: '',
+      },
+      {
         key: 'spellcheck.blocked_dictionaries',
         type: chrome.settingsPrivate.PrefType.LIST,
         value: [],
diff --git a/chrome/test/mini_installer/config/chrome_beta_installed.prop b/chrome/test/mini_installer/config/chrome_beta_installed.prop
index 41573c7..e05eb06 100644
--- a/chrome/test/mini_installer/config/chrome_beta_installed.prop
+++ b/chrome/test/mini_installer/config/chrome_beta_installed.prop
@@ -18,6 +18,16 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY_BETA": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR_BETA\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --chrome-beta --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY_BETA": {
       "exists": "required",
       "values": {
@@ -26,7 +36,11 @@
           "data": "$MINI_INSTALLER_FILE_VERSION"
         },
         "opv": { },
-        "cmd": { }
+        "cmd": { },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
+        }
       },
       "wow_key": "KEY_WOW64_32KEY"
     },
diff --git a/chrome/test/mini_installer/config/chrome_canary_installed.prop b/chrome/test/mini_installer/config/chrome_canary_installed.prop
index 6e51e5e..57ec7d6d 100644
--- a/chrome/test/mini_installer/config/chrome_canary_installed.prop
+++ b/chrome/test/mini_installer/config/chrome_canary_installed.prop
@@ -18,6 +18,16 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY_SXS": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR_SXS\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --chrome-sxs --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY_SXS": {
       "exists": "required",
       "values": {
@@ -26,7 +36,11 @@
           "data": "$MINI_INSTALLER_FILE_VERSION"
         },
         "opv": { },
-        "cmd": { }
+        "cmd": { },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
+        }
       },
       "wow_key": "KEY_WOW64_32KEY"
     },
diff --git a/chrome/test/mini_installer/config/chrome_dev_installed.prop b/chrome/test/mini_installer/config/chrome_dev_installed.prop
index badfae3..b01e2b1 100644
--- a/chrome/test/mini_installer/config/chrome_dev_installed.prop
+++ b/chrome/test/mini_installer/config/chrome_dev_installed.prop
@@ -18,6 +18,16 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR_DEV\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --chrome-dev --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY_DEV": {
       "exists": "required",
       "values": {
@@ -26,7 +36,11 @@
           "data": "$MINI_INSTALLER_FILE_VERSION"
         },
         "opv": { },
-        "cmd": { }
+        "cmd": { },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
+        }
       },
       "wow_key": "KEY_WOW64_32KEY"
     },
diff --git a/chrome/test/mini_installer/config/chrome_system_installed.prop b/chrome/test/mini_installer/config/chrome_system_installed.prop
index 235d383f..1517759 100644
--- a/chrome/test/mini_installer/config/chrome_system_installed.prop
+++ b/chrome/test/mini_installer/config/chrome_system_installed.prop
@@ -25,6 +25,16 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_LOCAL_MACHINE\\$CHROME_CLIENT_STATE_KEY": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --system-level --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_LOCAL_MACHINE\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
       "exists": "required",
       "values": {
@@ -33,7 +43,11 @@
           "data": "$MINI_INSTALLER_FILE_VERSION"
         },
         "opv": { },
-        "cmd": { }
+        "cmd": { },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
+        }
       },
       "wow_key": "KEY_WOW64_32KEY"
     },
diff --git a/chrome/test/mini_installer/config/chrome_user_installed.prop b/chrome/test/mini_installer/config/chrome_user_installed.prop
index 0cf0534..807ce88 100644
--- a/chrome/test/mini_installer/config/chrome_user_installed.prop
+++ b/chrome/test/mini_installer/config/chrome_user_installed.prop
@@ -17,6 +17,16 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
       "exists": "required",
       "values": {
@@ -25,7 +35,11 @@
           "data": "$MINI_INSTALLER_FILE_VERSION"
         },
         "opv": { },
-        "cmd": { }
+        "cmd": { },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
+        }
       },
       "wow_key": "KEY_WOW64_32KEY"
     },
diff --git a/chrome/test/mini_installer/config/previous_chrome_canary_installed.prop b/chrome/test/mini_installer/config/previous_chrome_canary_installed.prop
index 6833c3c..508e328 100644
--- a/chrome/test/mini_installer/config/previous_chrome_canary_installed.prop
+++ b/chrome/test/mini_installer/config/previous_chrome_canary_installed.prop
@@ -12,12 +12,26 @@
     "$LOCAL_APPDATA\\$CHROME_DIR_SXS\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION": {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY_SXS": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR_SXS\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --chrome-sxs --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY_SXS": {
       "exists": "required",
       "values": {
         "pv": {
           "type": "SZ",
           "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
+        },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
         }
       },
       "wow_key": "KEY_WOW64_32KEY"
diff --git a/chrome/test/mini_installer/config/previous_chrome_system_installed.prop b/chrome/test/mini_installer/config/previous_chrome_system_installed.prop
index 51728b5f..b07303b5 100644
--- a/chrome/test/mini_installer/config/previous_chrome_system_installed.prop
+++ b/chrome/test/mini_installer/config/previous_chrome_system_installed.prop
@@ -25,12 +25,26 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_LOCAL_MACHINE\\$CHROME_CLIENT_STATE_KEY": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$PROGRAM_FILES\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --system-level --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_LOCAL_MACHINE\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
       "exists": "required",
       "values": {
         "pv": {
           "type": "SZ",
           "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
+        },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
         }
       },
       "wow_key": "KEY_WOW64_32KEY"
diff --git a/chrome/test/mini_installer/config/previous_chrome_user_installed.prop b/chrome/test/mini_installer/config/previous_chrome_user_installed.prop
index 3ae41cb..84c8a90 100644
--- a/chrome/test/mini_installer/config/previous_chrome_user_installed.prop
+++ b/chrome/test/mini_installer/config/previous_chrome_user_installed.prop
@@ -17,12 +17,26 @@
         {"exists": false}
   },
   "RegistryEntries": {
+    "HKEY_CURRENT_USER\\$CHROME_CLIENT_STATE_KEY": {
+      "exists": "required",
+      "values": {
+        "DowngradeCleanupCommand": {
+          "type": "SZ",
+          "data": "\"$LOCAL_APPDATA\\$CHROME_DIR\\Application\\$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION\\Installer\\setup.exe\" --cleanup-for-downgrade-version=$$1 --cleanup-for-downgrade-operation=$$2 --verbose-logging"
+        }
+      },
+      "wow_key": "KEY_WOW64_32KEY"
+    },
     "HKEY_CURRENT_USER\\$CHROME_UPDATE_REGISTRY_SUBKEY": {
       "exists": "required",
       "values": {
         "pv": {
           "type": "SZ",
           "data": "$PREVIOUS_VERSION_MINI_INSTALLER_FILE_VERSION"
+        },
+        "CleanInstallRequiredForVersionBelow": {
+          "type": "SZ",
+          "data": "$LAST_INSTALLER_BREAKING_VERSION"
         }
       },
       "wow_key": "KEY_WOW64_32KEY"
diff --git a/chrome/test/mini_installer/variable_expander.py b/chrome/test/mini_installer/variable_expander.py
index d9d845d1..83a657a 100644
--- a/chrome/test/mini_installer/variable_expander.py
+++ b/chrome/test/mini_installer/variable_expander.py
@@ -146,6 +146,8 @@
             Name for Chrome Dev.
         * $CHROME_ELEVATION_SERVICE_DISPLAY_NAME_SXS: Elevation Service Display
             Name for Chrome SxS.
+        * $LAST_INSTALLER_BREAKING_VERSION: The last installer version that had
+            breaking changes.
 
     Args:
       mini_installer_path: The path to a mini_installer.
@@ -163,6 +165,8 @@
         '-q' if quiet else '',
         'OUTPUT_DIR':
         '"--output-dir=%s"' % output_dir if output_dir else '',
+        'LAST_INSTALLER_BREAKING_VERSION':
+        '85.0.4169.0',
         'LOCAL_APPDATA':
         shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0),
         'LOG_FILE':
@@ -219,6 +223,15 @@
           'CHROME_UPDATE_REGISTRY_SUBKEY': (
             'Software\\Google\\Update\\Clients\\'
             '{8A69D345-D564-463c-AFF1-A69D9E530F96}'),
+          'CHROME_CLIENT_STATE_KEY_BETA': (
+            'Software\\Google\\Update\\ClientState\\'
+            '{8237E44A-0054-442C-B6B6-EA0509993955}'),
+          'CHROME_CLIENT_STATE_KEY_DEV': (
+            'Software\\Google\\Update\\ClientState\\'
+            '{401C381F-E0DE-4B85-8BD8-3F3F14FBDA57}'),
+          'CHROME_CLIENT_STATE_KEY_SXS': (
+            'Software\\Google\\Update\\ClientState\\'
+            '{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}'),
           'CHROME_CLIENT_STATE_KEY': (
             'Software\\Google\\Update\\ClientState\\'
             '{8A69D345-D564-463c-AFF1-A69D9E530F96}'),
@@ -282,8 +295,7 @@
             'Google Chrome Dev Elevation Service'
             ' (GoogleChromeDevElevationService)'),
           'CHROME_ELEVATION_SERVICE_DISPLAY_NAME_SXS': (
-            'Google Chrome Canary Elevation Service'
-            ' (GoogleChromeCanaryElevationService)'),
+            'Google Chrome Canary Elevation Service'),
       })
     elif mini_installer_product_name == 'Chromium Installer':
       self._variable_mapping.update({
diff --git a/chromecast/browser/accessibility/flutter/ax_tree_source_flutter.cc b/chromecast/browser/accessibility/flutter/ax_tree_source_flutter.cc
index 375c0ff0..5f93ac6b 100644
--- a/chromecast/browser/accessibility/flutter/ax_tree_source_flutter.cc
+++ b/chromecast/browser/accessibility/flutter/ax_tree_source_flutter.cc
@@ -317,6 +317,7 @@
     focus_event.event_type = ax::mojom::Event::kFocus;
     focus_event.id = focused_id_;
     focus_event.event_from = ax::mojom::EventFrom::kNone;
+    focus_event.event_from_action = ax::mojom::Action::kNone;
     events.push_back(std::move(focus_event));
   }
 
@@ -613,6 +614,7 @@
         focus_event.event_type = ax::mojom::Event::kFocus;
         focus_event.id = focused_id_;
         focus_event.event_from = ax::mojom::EventFrom::kNone;
+        focus_event.event_from_action = ax::mojom::Action::kNone;
       }
     }
   }
@@ -652,6 +654,7 @@
     focus_event.event_type = ax::mojom::Event::kFocus;
     focus_event.id = focused_id_;
     focus_event.event_from = ax::mojom::EventFrom::kNone;
+    focus_event.event_from_action = ax::mojom::Action::kNone;
   }
 }
 
diff --git a/chromecast/media/audio/capture_service/constants.h b/chromecast/media/audio/capture_service/constants.h
index 96b8728..9e9e7ea 100644
--- a/chromecast/media/audio/capture_service/constants.h
+++ b/chromecast/media/audio/capture_service/constants.h
@@ -6,6 +6,7 @@
 #define CHROMECAST_MEDIA_AUDIO_CAPTURE_SERVICE_CONSTANTS_H_
 
 #include <cstdint>
+#include <ostream>
 
 namespace chromecast {
 namespace media {
@@ -24,6 +25,24 @@
   LAST_FORMAT = PLANAR_FLOAT,
 };
 
+inline std::ostream& operator<<(std::ostream& os, SampleFormat sample_format) {
+  switch (sample_format) {
+    case SampleFormat::INTERLEAVED_INT16:
+      return os << "INTERLEAVED_INT16";
+    case SampleFormat::INTERLEAVED_INT32:
+      return os << "INTERLEAVED_INT32";
+    case SampleFormat::INTERLEAVED_FLOAT:
+      return os << "INTERLEAVED_FLOAT";
+    case SampleFormat::PLANAR_INT16:
+      return os << "PLANAR_INT16";
+    case SampleFormat::PLANAR_INT32:
+      return os << "PLANAR_INT32";
+    case SampleFormat::PLANAR_FLOAT:
+      return os << "PLANAR_FLOAT";
+    // Don't use default, so compiler can capture missed enums.
+  }
+}
+
 enum class StreamType : uint8_t {
   // Raw microphone capture from ALSA or other platform interface.
   kMicRaw = 0,
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 4250e49..d2d85f3 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13852.0.0
\ No newline at end of file
+13853.0.0
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/DIR_METADATA b/chromeos/components/camera_app_ui/DIR_METADATA
index 3a2bdcc..0289c59 100644
--- a/chromeos/components/camera_app_ui/DIR_METADATA
+++ b/chromeos/components/camera_app_ui/DIR_METADATA
@@ -1,4 +1,4 @@
-monorail {
-  component: "Platform>Apps>Camera"
+buganizer {
+  component_id: 978428
 }
-team_email: "chromeos-camera@chromium.org"
+team_email: "chromeos-camera-eng@google.com"
diff --git a/chromeos/components/camera_app_ui/resources/.eslintrc.js b/chromeos/components/camera_app_ui/resources/.eslintrc.js
index 05e14ce2..b1c62b94 100644
--- a/chromeos/components/camera_app_ui/resources/.eslintrc.js
+++ b/chromeos/components/camera_app_ui/resources/.eslintrc.js
@@ -373,7 +373,6 @@
   'globals': {
     'arc': 'readable',
     'chromeosCamera': 'readable',
-    'blink': 'readable',
     'cros': 'readable',
     'trustedTypes': 'readable',
     'BarcodeDetector': 'readable',
diff --git a/chromeos/components/camera_app_ui/resources/js/BUILD.gn b/chromeos/components/camera_app_ui/resources/js/BUILD.gn
index 3bf4d369..e4774fe 100644
--- a/chromeos/components/camera_app_ui/resources/js/BUILD.gn
+++ b/chromeos/components/camera_app_ui/resources/js/BUILD.gn
@@ -58,7 +58,6 @@
     "//components/arc/mojom:camera_intent_js_library_for_compile",
     "//media/capture/mojom:image_capture_js_library_for_compile",
     "//media/capture/video/chromeos/mojom:cros_camera_js_library_for_compile",
-    "//third_party/blink/public/mojom:mojom_platform_js_library_for_compile",
   ]
   externs_list = [
     "$externs_path/chrome_extensions.js",
diff --git a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js b/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js
deleted file mode 100644
index a696dbe..0000000
--- a/chromeos/components/camera_app_ui/resources/js/browser_proxy/browser_proxy.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import * as localStorage from '../models/local_storage.js';
-
-// TODO(b/172343409): Remove this file once the usage in tests are updated.
-
-/**
- * @return {!Promise}
- */
-export async function localStorageClear() {
-  return localStorage.clear();
-}
-
-export const browserProxy = {localStorageClear};
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
index e0a6a6b..ffe3313 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
+++ b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
@@ -13,6 +13,16 @@
 window.loadTimeData;
 
 /**
+ * It is available since we enabled IdleDetection in origin trials map of CCA.
+ * @typedef {{
+ *   addEventListener: function(string, function()),
+ *   screenState: string,
+ *   start: function(): !Promise<void>,
+ * }}
+ */
+window.IdleDetector;
+
+/**
  * @typedef {{
  *   getDirectory: function(): !Promise<!FileSystemDirectoryHandle>,
  * }}
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
index c177c3b..100b0f1 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
+++ b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
@@ -9,7 +9,6 @@
 };
 
 declare var arc: MojomNamespace;
-declare var blink: MojomNamespace;
 declare var chromeosCamera: MojomNamespace;
 declare var cros: MojomNamespace;
 
diff --git a/chromeos/components/camera_app_ui/resources/js/js.gni b/chromeos/components/camera_app_ui/resources/js/js.gni
index 13786ba..1b761bdd 100644
--- a/chromeos/components/camera_app_ui/resources/js/js.gni
+++ b/chromeos/components/camera_app_ui/resources/js/js.gni
@@ -7,7 +7,6 @@
   "app_window.js",
   "async_job_queue.js",
   "barcode_chip.js",
-  "browser_proxy/browser_proxy.js",
   "chrome_util.js",
   "device/camera3_device_info.js",
   "device/constraints_preferrer.js",
@@ -81,7 +80,6 @@
   "views/warning.js",
   "waitable_event.js",
   "window_controller.js",
-  "window_controller/window_controller.js",
 ]
 
 no_compile_js_files = [
diff --git a/chromeos/components/camera_app_ui/resources/js/mojo/chrome_helper.js b/chromeos/components/camera_app_ui/resources/js/mojo/chrome_helper.js
index 99b73b4f..2212e4b 100644
--- a/chromeos/components/camera_app_ui/resources/js/mojo/chrome_helper.js
+++ b/chromeos/components/camera_app_ui/resources/js/mojo/chrome_helper.js
@@ -229,31 +229,6 @@
   }
 
   /**
-   * Adds listener for screen locked event.
-   * @param {function(boolean): void} callback Callback for screen locked status
-   *     changed. Called with the latest status of whether screen is locked.
-   */
-  async addOnLockListener(callback) {
-    const monitorCallbackRouter = new blink.mojom.IdleMonitorCallbackRouter();
-    closeWhenUnload(monitorCallbackRouter);
-    monitorCallbackRouter.update.addListener((newState) => {
-      callback(newState.screen === blink.mojom.ScreenIdleState.kLocked);
-    });
-
-    const idleManager = blink.mojom.IdleManager.getRemote();
-    closeWhenUnload(idleManager);
-    // Set a large threshold since we don't care about user idle. Note that
-    // ESLint does not yet seem to know about BigInt, so it complains about an
-    // uppercase "function" being used as something other than a constructor,
-    // and about BigInt not existing.
-    // eslint-disable-next-line new-cap, no-undef
-    const threshold = {microseconds: BigInt(86400000000)};
-    const {state} = await idleManager.addMonitor(
-        threshold, monitorCallbackRouter.$.bindNewPipeAndPassRemote());
-    callback(state.screen === blink.mojom.ScreenIdleState.kLocked);
-  }
-
-  /**
    * Checks if the logging consent option is enabled.
    * @return {!Promise<boolean>}
    */
diff --git a/chromeos/components/camera_app_ui/resources/js/type.js b/chromeos/components/camera_app_ui/resources/js/type.js
index 8b8a3904..6ce6f083 100644
--- a/chromeos/components/camera_app_ui/resources/js/type.js
+++ b/chromeos/components/camera_app_ui/resources/js/type.js
@@ -212,6 +212,7 @@
  */
 export const ErrorType = {
   BROKEN_THUMBNAIL: 'broken-thumbnail',
+  IDLE_DETECTOR_FAILURE: 'idle-detector-failure',
   PRELOAD_IMAGE_FAILURE: 'preload-image-failure',
   SET_FPS_RANGE_FAILURE: 'set-fps-range-failure',
   UNCAUGHT_PROMISE: 'uncaught-promise',
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera.js b/chromeos/components/camera_app_ui/resources/js/views/camera.js
index 8b937c4..7c0c2616 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera.js
@@ -14,6 +14,7 @@
 // eslint-disable-next-line no-unused-vars
 import {DeviceInfoUpdater} from '../device/device_info_updater.js';
 import * as dom from '../dom.js';
+import * as error from '../error.js';
 import * as metrics from '../metrics.js';
 import * as localStorage from '../models/local_storage.js';
 // eslint-disable-next-line no-unused-vars
@@ -26,6 +27,7 @@
 import * as sound from '../sound.js';
 import * as state from '../state.js';
 import * as toast from '../toast.js';
+import {ErrorLevel, ErrorType} from '../type.js';
 import {
   CanceledError,
   Facing,
@@ -278,12 +280,18 @@
         });
 
     // Monitor the states to stop camera when locked/minimized.
-    ChromeHelper.getInstance().addOnLockListener((isLocked) => {
-      this.locked_ = isLocked;
+    const idleDetector = new window.IdleDetector();
+    idleDetector.addEventListener('change', () => {
+      this.locked_ = idleDetector.screenState === 'locked';
       if (this.locked_) {
         this.start();
       }
     });
+    idleDetector.start().catch((e) => {
+      error.reportError(
+          ErrorType.IDLE_DETECTOR_FAILURE, ErrorLevel.ERROR,
+          assertInstanceof(e, Error));
+    });
 
     document.addEventListener('visibilitychange', () => {
       const recording = state.get(state.State.TAKING) && state.get(Mode.VIDEO);
diff --git a/chromeos/components/camera_app_ui/resources/js/window_controller/window_controller.js b/chromeos/components/camera_app_ui/resources/js/window_controller/window_controller.js
deleted file mode 100644
index 1197dfa3..0000000
--- a/chromeos/components/camera_app_ui/resources/js/window_controller/window_controller.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2021 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 should be removed once the usage in tests are updated.
-
-export {windowController} from '../window_controller.js';
diff --git a/chromeos/components/camera_app_ui/resources/views/main.html b/chromeos/components/camera_app_ui/resources/views/main.html
index dc6f937..b648feb 100644
--- a/chromeos/components/camera_app_ui/resources/views/main.html
+++ b/chromeos/components/camera_app_ui/resources/views/main.html
@@ -11,7 +11,6 @@
     <link rel="stylesheet" href="/css/main.css">
     <script src="/js/mojo/mojo_bindings_lite.js"></script>
     <script src="/js/mojo/time.mojom-lite.js"></script>
-    <script src="/js/mojo/idle_manager.mojom-lite.js"></script>
     <script src="/js/mojo/camera_metadata_tags.mojom-lite.js"></script>
     <script src="/js/mojo/camera_metadata.mojom-lite.js"></script>
     <script src="/js/mojo/camera_common.mojom-lite.js"></script>
diff --git a/chromeos/components/eche_app_ui/resources/mock/index.html b/chromeos/components/eche_app_ui/resources/mock/index.html
index 455c3e7..ab6ccd0e 100644
--- a/chromeos/components/eche_app_ui/resources/mock/index.html
+++ b/chromeos/components/eche_app_ui/resources/mock/index.html
@@ -4,7 +4,7 @@
 <!DOCTYPE html>
 <html>
   <head>
-    <title>Eche App</title>
+    <title>Eche</title>
     <header id='title'>Eche App</header>
     <meta charset="utf-8">
     <link rel="icon" href="system_assets/app_icon_32.png" sizes="32x32" type="image/png" />
diff --git a/chromeos/components/eche_app_ui/test/eche_app_ui_browsertest.js b/chromeos/components/eche_app_ui/test/eche_app_ui_browsertest.js
index ba91ef6..33ffb6b0 100644
--- a/chromeos/components/eche_app_ui/test/eche_app_ui_browsertest.js
+++ b/chromeos/components/eche_app_ui/test/eche_app_ui_browsertest.js
@@ -28,12 +28,10 @@
   }
 };
 
-// Tests that chrome://eche-app runs js file and that it goes
-// somewhere instead of 404ing or crashing.
-// Failing, see crbug.com/1185641
-TEST_F('EcheAppUIBrowserTest', 'DISABLED_HasChromeSchemeURL', () => {
-  const header = document.querySelector('header');
-
-  assertEquals(header.innerText, 'Eche App');
+// Tests that chrome://eche-app goes somewhere instead of
+// 404ing or crashing.
+TEST_F('EcheAppUIBrowserTest', 'HasChromeSchemeURL', () => {
+  assertEquals(document.title, 'Eche');
   assertEquals(document.location.origin, HOST_ORIGIN);
+  testDone();
 });
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 5df0b042..f3f4dc8 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -24,16 +24,21 @@
     "select_file.mojom",
     "test_controller.mojom",
     "url_handler.mojom",
+    "video_capture.mojom",
   ]
   disable_variants = true
 
   public_deps = [
     "//chromeos/components/sensors/mojom",
     "//chromeos/services/machine_learning/public/mojom",
+    "//media/capture/mojom:image_capture",
+    "//media/capture/mojom:video_capture_types",
     "//mojo/public/mojom/base",
     "//services/device/public/mojom:mojom",
     "//services/media_session/public/mojom:mojom",
+    "//ui/gfx/geometry/mojom",
     "//ui/gfx/image/mojom",
+    "//ui/gfx/mojom",
     "//url/mojom:url_mojom_gurl",
   ]
 }
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 317072e..e034a3e 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -21,6 +21,7 @@
 import "chromeos/crosapi/mojom/test_controller.mojom";
 import "chromeos/crosapi/mojom/url_handler.mojom";
 import "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom";
+import "chromeos/crosapi/mojom/video_capture.mojom";
 import "mojo/public/mojom/base/big_string.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
 import "mojo/public/mojom/base/token.mojom";
@@ -49,8 +50,8 @@
 // please note the milestone when you added it, to help us reason about
 // compatibility between the client applications and older ash-chrome binaries.
 //
-// Next version: 18
-// Next method id: 23
+// Next version: 19
+// Next method id: 24
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e",
  RenamedFrom="crosapi.mojom.AshChromeService"]
 interface Crosapi {
@@ -167,6 +168,11 @@
   // Added in M90.
   [MinVersion=13] BindUrlHandler@18(pending_receiver<UrlHandler> receiver);
 
+  // Binds the device factory in video capture service.
+  // Added in M90.
+  [MinVersion=18] BindVideoCaptureDeviceFactory@23(
+      pending_receiver<crosapi.mojom.VideoCaptureDeviceFactory> receiver);
+
   // Passes generic browser information such as version, etc into ash in
   // |browser_info| during startup.
   // Added in M87.
diff --git a/chromeos/crosapi/mojom/video_capture.mojom b/chromeos/crosapi/mojom/video_capture.mojom
new file mode 100644
index 0000000..0719bc8
--- /dev/null
+++ b/chromeos/crosapi/mojom/video_capture.mojom
@@ -0,0 +1,183 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module crosapi.mojom;
+
+import "media/capture/mojom/image_capture.mojom";
+import "media/capture/mojom/video_capture_types.mojom";
+import "mojo/public/mojom/base/shared_memory.mojom";
+import "mojo/public/mojom/base/time.mojom";
+import "mojo/public/mojom/base/unguessable_token.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
+import "ui/gfx/mojom/native_handle_types.mojom";
+
+// This API is used to forward requests/respond of video_capture::DeviceFactory
+// and video_capture::Device from Lacros (Client) to the actual implementation
+// in Ash-Chrome.
+// In addition, to avoid affecting too much on non-Chrome OS components and
+// depends on components which will cause cyclic dependency, we used some
+// similar but simplified version of structures rather than directly depends on
+// the structures in other components.
+
+// Similar to |gfx.mojom.NativePixmapHandle| but does not contain fields which
+// are not available on Chrome OS.
+// Next min field ID: 2
+[Stable]
+struct NativePixmapHandle {
+  array<gfx.mojom.NativePixmapPlane> planes@0;
+
+  uint64 modifier@1;
+};
+
+// Similar to |gfx.mojom.GpuMemoryBufferPlatformHandle| but does not contain
+// fields which are not available on Chrome OS.
+// Next min field ID: 2
+[Stable]
+union GpuMemoryBufferPlatformHandle {
+  mojo_base.mojom.UnsafeSharedMemoryRegion shared_memory_handle@0;
+
+  NativePixmapHandle native_pixmap_handle@1;
+};
+
+// Similar to |gfx.mojom.GpuMemoryBufferHandle| but does not depend on
+// |gfx.mojom.GpuMemoryBufferPlatformHandle|.
+// Next min field ID: 4
+[Stable]
+struct GpuMemoryBufferHandle {
+  int32 id@0;
+
+  uint32 offset@1;
+
+  uint32 stride@2;
+
+  GpuMemoryBufferPlatformHandle? platform_handle@3;
+};
+
+// Similar to |media.mojom.VideoBufferHandle| but does not contain fields which
+// are not used in Chrome OS implementation.
+// Next min field ID: 2
+[Stable]
+union VideoBufferHandle {
+  handle<shared_buffer> shared_buffer_handle@0;
+
+  GpuMemoryBufferHandle gpu_memory_buffer_handle@1;
+};
+
+// Identical to |video_capture.mojom.DeviceAccessResultCode|.
+[Stable, Extensible]
+enum DeviceAccessResultCode {
+  NOT_INITIALIZED,
+  SUCCESS,
+  ERROR_DEVICE_NOT_FOUND
+};
+
+// Identical to |media.mojom.VideoRotation|.
+[Stable, Extensible]
+enum VideoRotation {
+  kVideoRotation0,
+  kVideoRotation90,
+  kVideoRotation180,
+  kVideoRotation270,
+};
+
+// Similar to |media.mojom.VideoFrameInfo| but without some fields which does
+// not implement on Chrome OS. In addition, since most of the fields in
+// |metadata| field is not used in Chrome OS implementation, we also simplify
+// the structure by only containing |rotation| and |reference_time|.
+// Next min field ID: 6
+[Stable]
+struct VideoFrameInfo {
+  mojo_base.mojom.TimeDelta timestamp@0;
+
+  media.mojom.VideoCapturePixelFormat pixel_format@1;
+
+  gfx.mojom.Size coded_size@2;
+
+  gfx.mojom.Rect visible_rect@3;
+
+  // The following fields are the fields we may use for constructing
+  // media::VideoFrameMetadata.
+  VideoRotation rotation@4;
+
+  mojo_base.mojom.TimeTicks reference_time@5;
+};
+
+// Similar to |video_capture.mojom.ReadyFrameInBuffer| but does not depend on
+// |media.mojom.VideoFrameInfo| and
+// |video_capture.mojom.ScopedAccessPermission|.
+// Next min field ID: 4
+[Stable]
+struct ReadyFrameInBuffer {
+  int32 buffer_id@0;
+
+  int32 frame_feedback_id@1;
+
+  pending_remote<ScopedAccessPermission> access_permission@2;
+
+  VideoFrameInfo frame_info@3;
+};
+
+// Identical to |video_capture.mojom.ScopedAccessPermission|.
+// Next min method ID: 0
+[Stable, Uuid="bf0f3239-26d2-45f8-9875-490563f5af97"]
+interface ScopedAccessPermission {};
+
+// Similar to |video_capture.mojom.VideoFrameHandler| but depends on simplified
+// structures.
+// Next min method ID: 9
+[Stable, Uuid="590ab36a-9162-4c9d-8429-1753108825ea"]
+interface VideoFrameHandler {
+  OnNewBuffer@0(int32 buffer_id, VideoBufferHandle buffer_handle);
+
+  OnFrameReadyInBuffer@1(ReadyFrameInBuffer buffer,
+                         array<ReadyFrameInBuffer> scaled_buffers);
+
+  OnBufferRetired@2(int32 buffer_id);
+
+  OnError@3(media.mojom.VideoCaptureError error);
+
+  OnFrameDropped@4(media.mojom.VideoCaptureFrameDropReason reason);
+
+  OnLog@5(string message);
+
+  OnStarted@6();
+
+  OnStartedUsingGpuDecode@7();
+
+  OnStopped@8();
+};
+
+// Similar to |video_capture.mojom.Device| but depends on simplified structures.
+// Next min method ID: 7
+[Stable, Uuid="f50f1672-d512-451e-9c70-998ed45ab596"]
+interface VideoCaptureDevice {
+  // It is assumed that it will be called only once.
+  Start@0(media.mojom.VideoCaptureParams requested_settings,
+          pending_remote<VideoFrameHandler> handler);
+
+  MaybeSuspend@1();
+
+  Resume@2();
+
+  GetPhotoState@3() => (media.mojom.PhotoState? capabilities);
+
+  SetPhotoOptions@4(media.mojom.PhotoSettings settings) => (bool success);
+
+  TakePhoto@5() => (media.mojom.Blob? blob);
+
+  ProcessFeedback@6(media.mojom.VideoFrameFeedback feedback);
+};
+
+// Similar to |video_capture.mojom.DeviceFactory| but depends on simplified
+// structures.
+// Next min method ID: 2
+[Stable, Uuid="b79ed8be-cf39-4d0d-a819-2d299022124a"]
+interface VideoCaptureDeviceFactory {
+  GetDeviceInfos@0()
+      => (array<media.mojom.VideoCaptureDeviceInfo> device_infos);
+
+  CreateDevice@1(string device_id,
+                 pending_receiver<VideoCaptureDevice> device_receiver)
+      => (DeviceAccessResultCode result_code);
+};
diff --git a/chromeos/lacros/lacros_chrome_service_impl.cc b/chromeos/lacros/lacros_chrome_service_impl.cc
index b5dfbbf..5a9ce0e3 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.cc
+++ b/chromeos/lacros/lacros_chrome_service_impl.cc
@@ -309,6 +309,13 @@
     crosapi_->BindUrlHandler(std::move(pending_receiver));
   }
 
+  void BindVideoCaptureDeviceFactoryReceiver(
+      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory>
+          pending_receiver) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    crosapi_->BindVideoCaptureDeviceFactory(std::move(pending_receiver));
+  }
+
   base::WeakPtr<LacrosChromeServiceNeverBlockingState> GetWeakPtr() {
     return weak_factory_.GetWeakPtr();
   }
@@ -850,6 +857,25 @@
                         Crosapi::MethodMinVersions::kOnBrowserStartupMinVersion;
 }
 
+void LacrosChromeServiceImpl::BindVideoCaptureDeviceFactory(
+    mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory>
+        pending_receiver) {
+  DCHECK(IsVideoCaptureDeviceFactoryAvailable());
+
+  never_blocking_sequence_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&LacrosChromeServiceNeverBlockingState::
+                         BindVideoCaptureDeviceFactoryReceiver,
+                     weak_sequenced_state_, std::move(pending_receiver)));
+}
+
+bool LacrosChromeServiceImpl::IsVideoCaptureDeviceFactoryAvailable() const {
+  base::Optional<uint32_t> version = CrosapiVersion();
+  return version && version.value() >=
+                        Crosapi::MethodMinVersions::
+                            kBindVideoCaptureDeviceFactoryMinVersion;
+}
+
 int LacrosChromeServiceImpl::GetInterfaceVersion(
     base::Token interface_uuid) const {
   if (g_disable_all_crosapi_for_tests)
diff --git a/chromeos/lacros/lacros_chrome_service_impl.h b/chromeos/lacros/lacros_chrome_service_impl.h
index 7f90cfd..a93173a 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.h
+++ b/chromeos/lacros/lacros_chrome_service_impl.h
@@ -32,6 +32,7 @@
 #include "chromeos/crosapi/mojom/select_file.mojom.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom.h"
 #include "chromeos/crosapi/mojom/url_handler.mojom.h"
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -273,6 +274,15 @@
   // returns true.
   bool IsOnBrowserStartupAvailable() const;
 
+  // Binds video capture host.
+  void BindVideoCaptureDeviceFactory(
+      mojo::PendingReceiver<crosapi::mojom::VideoCaptureDeviceFactory>
+          pending_receiver);
+
+  // BindVideoCaptureDeviceFactory() can only be used if this method returns
+  // true.
+  bool IsVideoCaptureDeviceFactoryAvailable() const;
+
   // Returns BrowserInitParams which is passed from ash-chrome. On launching
   // lacros-chrome from ash-chrome, ash-chrome creates a memory backed file
   // serializes the BrowserInitParams to it, and the forked/executed
diff --git a/chromeos/system/BUILD.gn b/chromeos/system/BUILD.gn
index e973cf7b..b9dee8c 100644
--- a/chromeos/system/BUILD.gn
+++ b/chromeos/system/BUILD.gn
@@ -2,39 +2,48 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-assert(is_chromeos, "Non-Chrome-OS builds must not depend on //chromeos")
+import("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos,
+       "Non-Chrome-OS or Lacros builds must not depend on //chromeos")
 
 component("system") {
   output_name = "chromeos_system"
   defines = [ "IS_CHROMEOS_SYSTEM_IMPL" ]
-  deps = [
-    "//ash/constants",
-    "//base",
-    "//chromeos:chromeos_export",
-    "//ui/ozone:ozone_base",
-  ]
+  deps = [ "//base" ]
   sources = [
-    "core_scheduling.cc",
-    "core_scheduling.h",
     "cpu_temperature_reader.cc",
     "cpu_temperature_reader.h",
-    "devicemode.cc",
-    "devicemode.h",
-    "factory_ping_embargo_check.cc",
-    "factory_ping_embargo_check.h",
-    "kiosk_oem_manifest_parser.cc",
-    "kiosk_oem_manifest_parser.h",
-
-    # Used when running mash, both on Linux and on real devices.
-    "fake_statistics_provider.cc",
-    "fake_statistics_provider.h",
-    "name_value_pairs_parser.cc",
-    "name_value_pairs_parser.h",
-    "scheduler_configuration_manager_base.cc",
-    "scheduler_configuration_manager_base.h",
-    "statistics_provider.cc",
-    "statistics_provider.h",
   ]
+
+  if (is_chromeos_ash) {
+    deps += [
+      "//ash/constants",
+      "//base",
+      "//chromeos:chromeos_export",
+      "//ui/ozone:ozone_base",
+    ]
+    sources += [
+      "core_scheduling.cc",
+      "core_scheduling.h",
+      "devicemode.cc",
+      "devicemode.h",
+      "factory_ping_embargo_check.cc",
+      "factory_ping_embargo_check.h",
+      "kiosk_oem_manifest_parser.cc",
+      "kiosk_oem_manifest_parser.h",
+
+      # Used when running mash, both on Linux and on real devices.
+      "fake_statistics_provider.cc",
+      "fake_statistics_provider.h",
+      "name_value_pairs_parser.cc",
+      "name_value_pairs_parser.h",
+      "scheduler_configuration_manager_base.cc",
+      "scheduler_configuration_manager_base.h",
+      "statistics_provider.cc",
+      "statistics_provider.h",
+    ]
+  }
 }
 
 source_set("unit_tests") {
@@ -42,14 +51,19 @@
   deps = [
     ":system",
     "//base",
-    "//base/test:test_support",
-    "//chromeos:test_utils",
     "//testing/gtest",
   ]
-  sources = [
-    "cpu_temperature_reader_unittest.cc",
-    "factory_ping_embargo_check_unittest.cc",
-    "kiosk_oem_manifest_parser_unittest.cc",
-    "name_value_pairs_parser_unittest.cc",
-  ]
+  sources = [ "cpu_temperature_reader_unittest.cc" ]
+
+  if (is_chromeos_ash) {
+    deps += [
+      "//base/test:test_support",
+      "//chromeos:test_utils",
+    ]
+    sources += [
+      "factory_ping_embargo_check_unittest.cc",
+      "kiosk_oem_manifest_parser_unittest.cc",
+      "name_value_pairs_parser_unittest.cc",
+    ]
+  }
 }
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 92bf0af..b2e862f 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -348,6 +348,8 @@
     "test/fake_dark_theme_instance.h",
     "test/fake_file_system_instance.cc",
     "test/fake_file_system_instance.h",
+    "test/fake_iio_sensor_instance.cc",
+    "test/fake_iio_sensor_instance.h",
     "test/fake_intent_helper_instance.cc",
     "test/fake_intent_helper_instance.h",
     "test/fake_pip_instance.cc",
@@ -419,6 +421,7 @@
     "pay/arc_payment_app_bridge_unittest.cc",
     "power/arc_power_bridge_unittest.cc",
     "property/arc_property_bridge_unittest.cc",
+    "sensor/arc_iio_sensor_bridge_unittest.cc",
     "session/arc_container_client_adapter_unittest.cc",
     "session/arc_data_remover_unittest.cc",
     "session/arc_session_impl_unittest.cc",
diff --git a/components/arc/mojom/iio_sensor.mojom b/components/arc/mojom/iio_sensor.mojom
index f4685107..1e4fdce20 100644
--- a/components/arc/mojom/iio_sensor.mojom
+++ b/components/arc/mojom/iio_sensor.mojom
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Next MinVersion: 2
 module arc.mojom;
 
 import "chromeos/components/sensors/mojom/cros_sensor_service.mojom";
@@ -16,8 +17,12 @@
 };
 
 // ARC implements this interface to interact with chrome.
-// Next method ID: 1
+// Next method ID: 2
 interface IioSensorInstance {
   // Establishes full-duplex communication with the host.
   Init@0(pending_remote<IioSensorHost> host_remote) => ();
+
+  // Called when the device enters or leaves the tablet mode.
+  // ARC uses this info to adjust the direction of sensor measurements.
+  [MinVersion=1] OnTabletModeChanged@1(bool is_tablet_mode_on);
 };
diff --git a/components/arc/sensor/arc_iio_sensor_bridge.cc b/components/arc/sensor/arc_iio_sensor_bridge.cc
index 6b331a94..7f651f88 100644
--- a/components/arc/sensor/arc_iio_sensor_bridge.cc
+++ b/components/arc/sensor/arc_iio_sensor_bridge.cc
@@ -45,11 +45,19 @@
 ArcIioSensorBridge::ArcIioSensorBridge(content::BrowserContext* context,
                                        ArcBridgeService* bridge_service)
     : arc_bridge_service_(bridge_service) {
+  chromeos::PowerManagerClient::Get()->AddObserver(this);
+  arc_bridge_service_->iio_sensor()->AddObserver(this);
   arc_bridge_service_->iio_sensor()->SetHost(this);
+
+  // Get the current tablet mode.
+  chromeos::PowerManagerClient::Get()->GetSwitchStates(base::BindOnce(
+      &ArcIioSensorBridge::OnGetSwitchStates, weak_ptr_factory_.GetWeakPtr()));
 }
 
 ArcIioSensorBridge::~ArcIioSensorBridge() {
   arc_bridge_service_->iio_sensor()->SetHost(nullptr);
+  arc_bridge_service_->iio_sensor()->RemoveObserver(this);
+  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
 }
 
 void ArcIioSensorBridge::RegisterSensorHalClient(
@@ -58,4 +66,36 @@
       std::move(remote));
 }
 
+void ArcIioSensorBridge::OnConnectionReady() {
+  // Send the current tablet mode just after initialization.
+  if (is_tablet_mode_on_.has_value())
+    SendTabletMode();
+}
+
+void ArcIioSensorBridge::TabletModeEventReceived(
+    chromeos::PowerManagerClient::TabletMode mode,
+    base::TimeTicks timestamp) {
+  SetIsTabletModeOn(mode == chromeos::PowerManagerClient::TabletMode::ON);
+}
+
+void ArcIioSensorBridge::SendTabletMode() {
+  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
+      arc_bridge_service_->iio_sensor(), OnTabletModeChanged);
+  if (instance)
+    instance->OnTabletModeChanged(is_tablet_mode_on_.value());
+}
+
+void ArcIioSensorBridge::SetIsTabletModeOn(bool is_tablet_mode_on) {
+  is_tablet_mode_on_ = is_tablet_mode_on;
+  SendTabletMode();
+}
+
+void ArcIioSensorBridge::OnGetSwitchStates(
+    base::Optional<chromeos::PowerManagerClient::SwitchStates> states) {
+  if (states.has_value()) {
+    SetIsTabletModeOn(states->tablet_mode ==
+                      chromeos::PowerManagerClient::TabletMode::ON);
+  }
+}
+
 }  // namespace arc
diff --git a/components/arc/sensor/arc_iio_sensor_bridge.h b/components/arc/sensor/arc_iio_sensor_bridge.h
index 343a827..9a2d6cd 100644
--- a/components/arc/sensor/arc_iio_sensor_bridge.h
+++ b/components/arc/sensor/arc_iio_sensor_bridge.h
@@ -5,7 +5,11 @@
 #ifndef COMPONENTS_ARC_SENSOR_ARC_IIO_SENSOR_BRIDGE_H_
 #define COMPONENTS_ARC_SENSOR_ARC_IIO_SENSOR_BRIDGE_H_
 
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chromeos/dbus/power/power_manager_client.h"
 #include "components/arc/mojom/iio_sensor.mojom.h"
+#include "components/arc/session/connection_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace content {
@@ -17,7 +21,10 @@
 class ArcBridgeService;
 
 // This class handles Sensor-related requests from the ARC container.
-class ArcIioSensorBridge : public KeyedService, public mojom::IioSensorHost {
+class ArcIioSensorBridge : public KeyedService,
+                           public mojom::IioSensorHost,
+                           public ConnectionObserver<mojom::IioSensorInstance>,
+                           public chromeos::PowerManagerClient::Observer {
  public:
   // Returns singleton instance for the given BrowserContext,
   // or nullptr if the browser |context| is not allowed to use ARC.
@@ -35,8 +42,28 @@
       mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> remote)
       override;
 
+  // ConnectionObserver<mojom::IioSensorInstance> overrides:
+  void OnConnectionReady() override;
+
+  // chromeos::PowerManagerClient::Observer overrides:
+  void TabletModeEventReceived(chromeos::PowerManagerClient::TabletMode mode,
+                               base::TimeTicks timestamp) override;
+
  private:
+  // Send tablet mode changed event to ARC.
+  void SendTabletMode();
+
+  // Sets is_tablet_mode_on and sends the event to ARC.
+  void SetIsTabletModeOn(bool is_tablet_mode_on);
+
+  // Called with PowerManagerClient::GetSwitchStates() result.
+  void OnGetSwitchStates(
+      base::Optional<chromeos::PowerManagerClient::SwitchStates> states);
+
   ArcBridgeService* const arc_bridge_service_;  // Owned by ArcServiceManager.
+  base::Optional<bool> is_tablet_mode_on_;
+
+  base::WeakPtrFactory<ArcIioSensorBridge> weak_ptr_factory_{this};
 };
 
 }  // namespace arc
diff --git a/components/arc/sensor/arc_iio_sensor_bridge_unittest.cc b/components/arc/sensor/arc_iio_sensor_bridge_unittest.cc
new file mode 100644
index 0000000..4c989285
--- /dev/null
+++ b/components/arc/sensor/arc_iio_sensor_bridge_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright 2021 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 "components/arc/sensor/arc_iio_sensor_bridge.h"
+
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "chromeos/dbus/power/fake_power_manager_client.h"
+#include "components/arc/session/arc_bridge_service.h"
+#include "components/arc/test/connection_holder_util.h"
+#include "components/arc/test/fake_iio_sensor_instance.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace arc {
+
+class ArcIioSensorBridgeTest : public testing::Test {
+ public:
+  ArcIioSensorBridgeTest() = default;
+  ~ArcIioSensorBridgeTest() override = default;
+  ArcIioSensorBridgeTest(const ArcIioSensorBridgeTest&) = delete;
+  ArcIioSensorBridgeTest& operator=(const ArcIioSensorBridgeTest&) = delete;
+
+  void SetUp() override {
+    chromeos::PowerManagerClient::InitializeFake();
+    bridge_ = std::make_unique<ArcIioSensorBridge>(nullptr /* context */,
+                                                   &bridge_service_);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void TearDown() override {
+    bridge_ = nullptr;
+    chromeos::PowerManagerClient::Shutdown();
+  }
+
+ protected:
+  void InitializeInstance() {
+    bridge_service_.iio_sensor()->SetInstance(&fake_instance_);
+    WaitForInstanceReady(bridge_service_.iio_sensor());
+  }
+
+  base::test::TaskEnvironment task_environment_;
+
+  ArcBridgeService bridge_service_;
+  FakeIioSensorInstance fake_instance_;
+  std::unique_ptr<ArcIioSensorBridge> bridge_;
+};
+
+TEST_F(ArcIioSensorBridgeTest, TabletMode) {
+  // Verify the fake's initial state.
+  EXPECT_FALSE(fake_instance_.is_tablet_mode_on());
+
+  // ARC receives the tablet mode update just after initialization.
+  chromeos::FakePowerManagerClient::Get()->SetTabletMode(
+      chromeos::PowerManagerClient::TabletMode::ON, base::TimeTicks());
+  InitializeInstance();
+  EXPECT_TRUE(fake_instance_.is_tablet_mode_on());
+
+  // ARC continues receiving updates after initialization.
+  chromeos::FakePowerManagerClient::Get()->SetTabletMode(
+      chromeos::PowerManagerClient::TabletMode::OFF, base::TimeTicks());
+  EXPECT_FALSE(fake_instance_.is_tablet_mode_on());
+
+  chromeos::FakePowerManagerClient::Get()->SetTabletMode(
+      chromeos::PowerManagerClient::TabletMode::ON, base::TimeTicks());
+  EXPECT_TRUE(fake_instance_.is_tablet_mode_on());
+}
+
+}  // namespace arc
diff --git a/components/arc/test/fake_iio_sensor_instance.cc b/components/arc/test/fake_iio_sensor_instance.cc
new file mode 100644
index 0000000..f342cb23
--- /dev/null
+++ b/components/arc/test/fake_iio_sensor_instance.cc
@@ -0,0 +1,24 @@
+// Copyright 2021 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 "components/arc/test/fake_iio_sensor_instance.h"
+
+namespace arc {
+
+FakeIioSensorInstance::FakeIioSensorInstance() = default;
+FakeIioSensorInstance::~FakeIioSensorInstance() = default;
+
+void FakeIioSensorInstance::Init(
+    mojo::PendingRemote<mojom::IioSensorHost> host_remote,
+    InitCallback callback) {
+  host_remote_.reset();
+  host_remote_.Bind(std::move(host_remote));
+  std::move(callback).Run();
+}
+
+void FakeIioSensorInstance::OnTabletModeChanged(bool is_tablet_mode_on) {
+  is_tablet_mode_on_ = is_tablet_mode_on;
+}
+
+}  // namespace arc
diff --git a/components/arc/test/fake_iio_sensor_instance.h b/components/arc/test/fake_iio_sensor_instance.h
new file mode 100644
index 0000000..87ce1ce
--- /dev/null
+++ b/components/arc/test/fake_iio_sensor_instance.h
@@ -0,0 +1,36 @@
+// Copyright 2021 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 COMPONENTS_ARC_TEST_FAKE_IIO_SENSOR_INSTANCE_H_
+#define COMPONENTS_ARC_TEST_FAKE_IIO_SENSOR_INSTANCE_H_
+
+#include "components/arc/mojom/iio_sensor.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace arc {
+
+class FakeIioSensorInstance : public mojom::IioSensorInstance {
+ public:
+  FakeIioSensorInstance();
+  ~FakeIioSensorInstance() override;
+  FakeIioSensorInstance(const FakeIioSensorInstance&) = delete;
+  FakeIioSensorInstance& operator=(const FakeIioSensorInstance&) = delete;
+
+  bool is_tablet_mode_on() const { return is_tablet_mode_on_; }
+
+  // mojom::IioSensorInstance overrides:
+  void Init(mojo::PendingRemote<mojom::IioSensorHost> host_remote,
+            InitCallback callback) override;
+  void OnTabletModeChanged(bool is_tablet_mode_on) override;
+
+ private:
+  mojo::Remote<mojom::IioSensorHost> host_remote_;
+
+  bool is_tablet_mode_on_ = false;
+};
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_TEST_FAKE_IIO_SENSOR_INSTANCE_H_
diff --git a/components/browser_ui/widget/android/java/res/layout/textbubble_text_with_image.xml b/components/browser_ui/widget/android/java/res/layout/textbubble_text_with_image.xml
index 49ec705e..7c131d7 100644
--- a/components/browser_ui/widget/android/java/res/layout/textbubble_text_with_image.xml
+++ b/components/browser_ui/widget/android/java/res/layout/textbubble_text_with_image.xml
@@ -28,6 +28,22 @@
         android:scaleType="centerInside"
         app:tint="@color/default_icon_color_inverse" />
 
+    <!-- The FrameLayout is to center the smaller LoadingView in the same space as the icon, as well
+     as for providing a contentDescription for the LoadingView. -->
+    <FrameLayout
+        android:id="@+id/loading_view_container"
+        android:layout_width="24sp"
+        android:layout_height="24sp"
+        android:layout_marginEnd="8dp"
+        android:visibility="gone">
+        <org.chromium.components.browser_ui.widget.LoadingView
+            android:id="@+id/loading_view"
+            android:layout_width="16sp"
+            android:layout_height="16sp"
+            android:layout_gravity="center"
+            android:indeterminateTint="@color/default_icon_color_inverse" />
+    </FrameLayout>
+
     <TextView
         android:id="@+id/message"
         android:layout_width="wrap_content"
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/ClickableTextBubble.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/ClickableTextBubble.java
index 641d7ba..d9e8fe77 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/ClickableTextBubble.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/ClickableTextBubble.java
@@ -9,6 +9,8 @@
 import androidx.annotation.DrawableRes;
 import androidx.annotation.StringRes;
 
+import org.chromium.components.browser_ui.widget.LoadingView;
+import org.chromium.components.browser_ui.widget.R;
 import org.chromium.ui.widget.RectProvider;
 
 /**
@@ -21,9 +23,35 @@
  *     <li>Smaller padding
  *     //TODO(sophey): Implement shadow once 9-patches are available.
  *     <li>Shadow
+ *     <li>Optional loading UI
  * </ul>
+ *
+ * <p>A loading UI using {@link LoadingView} can be shown using {@link #showLoadingUI(int)}. This
+ * should be used if there is a possibility of a response time >500ms, after which the loading
+ * view will show. To hide the LoadingView and dismiss the bubble, call
+ * {@link #hideLoadingUI(LoadingView.Observer)}, which takes in a {@link LoadingView.Observer},
+ * for when further actions should be taken after the UI is hidden (such as showing another UI
+ * element). Example below:
+ *
+ *  <pre>{@code
+ *      ClickableTextBubble clickableTextBubble;
+ *      OnTouchListener onTouchListener = (view, motionEvent) -> {
+ *          performPotentiallyLongRequest();
+ *          clickableTextBubble.showLoadingUI(loadingViewContentDescriptionId);
+ *      };
+ *
+ *      void potentiallyLongRequestFinished() {
+ *          clickableTextBubble.hideLoadingUI(new LoadingView.Observer() {
+ *              public void onHideLoadingUIComplete() {
+ *                  // show another UI element (eg. bubble, snackbar)
+ *              }
+ *          }
+ *      }
+ *  }</pre>
  */
 public class ClickableTextBubble extends TextBubble {
+    private final LoadingView mLoadingView;
+
     /**
      * Constructs a {@link ClickableTextBubble} instance.
      *
@@ -42,8 +70,50 @@
             @DrawableRes int imageDrawableId, boolean isAccessibilityEnabled,
             View.OnTouchListener onTouchListener) {
         super(context, rootView, stringId, accessibilityStringId, /*showArrow=*/false,
-                anchorRectProvider, imageDrawableId, /*isRoundBubble=*/true,
+                anchorRectProvider, imageDrawableId, /*isRoundBubble=*/true, /*inverseColor=*/false,
                 isAccessibilityEnabled);
         setTouchInterceptor(onTouchListener);
+        mLoadingView = mContentView.findViewById(R.id.loading_view);
+    }
+
+    /**
+     * Replaces image with loading spinner. Dismisses the entire button when loading spinner is
+     * hidden.
+     *
+     * @param loadingViewContentDescriptionId ID of the ContentDescription for the loading spinner.
+     */
+    public void showLoadingUI(@StringRes int loadingViewContentDescriptionId) {
+        mLoadingView.addObserver(new LoadingView.Observer() {
+            @Override
+            public void onShowLoadingUIComplete() {
+                View loadingViewContainer = mContentView.findViewById(R.id.loading_view_container);
+                loadingViewContainer.setVisibility(View.VISIBLE);
+                loadingViewContainer.setContentDescription(
+                        mContext.getString(loadingViewContentDescriptionId));
+                mContentView.findViewById(R.id.image).setVisibility(View.GONE);
+                setAutoDismissTimeout(NO_TIMEOUT);
+            }
+
+            @Override
+            public void onHideLoadingUIComplete() {
+                dismiss();
+            }
+        });
+        mLoadingView.showLoadingUI();
+    }
+
+    /**
+     * Exposes {@link LoadingView#hideLoadingUI()} and adds a {@link LoadingView.Observer} to the
+     * {@link LoadingView}.
+     *
+     * @param loadingViewObserver Observer to add to the {@link LoadingView}.
+     */
+    public void hideLoadingUI(LoadingView.Observer loadingViewObserver) {
+        mLoadingView.addObserver(loadingViewObserver);
+        mLoadingView.hideLoadingUI();
+    }
+
+    public void destroy() {
+        mLoadingView.destroy();
     }
 }
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
index f96a3be..e67ddab 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
@@ -49,8 +49,9 @@
      */
     private static final Set<TextBubble> sBubbles = new HashSet<>();
 
-    private final Context mContext;
+    protected final Context mContext;
     private final Handler mHandler;
+    private final boolean mInverseColor;
 
     /** The actual {@link PopupWindow}.  Internalized to prevent API leakage. */
     private final AnchoredPopupWindow mPopupWindow;
@@ -91,7 +92,7 @@
     private final boolean mIsAccessibilityEnabled;
 
     /** The content view shown in the popup window. */
-    private View mContentView;
+    protected View mContentView;
 
     /**
      * Constructs a {@link TextBubble} instance using the default arrow drawable background. Creates
@@ -177,7 +178,8 @@
             RectProvider anchorRectProvider, boolean isAccessibilityEnabled) {
         this(context, rootView, context.getString(stringId),
                 context.getString(accessibilityStringId), showArrow, anchorRectProvider,
-                /*imageDrawable=*/null, /*isRoundBubble=*/false, isAccessibilityEnabled);
+                /*imageDrawable=*/null, /*isRoundBubble=*/false, /*inverseColor=*/false,
+                isAccessibilityEnabled);
     }
 
     /**
@@ -195,7 +197,8 @@
             String accessibilityString, boolean showArrow, RectProvider anchorRectProvider,
             boolean isAccessibilityEnabled) {
         this(context, rootView, contentString, accessibilityString, showArrow, anchorRectProvider,
-                /*imageDrawable=*/null, /*isRoundBubble=*/false, isAccessibilityEnabled);
+                /*imageDrawable=*/null, /*isRoundBubble=*/false, /*inverseColor=*/false,
+                isAccessibilityEnabled);
     }
 
     /**
@@ -209,17 +212,18 @@
      * @param anchorRectProvider The {@link RectProvider} used to anchor the text bubble.
      * @param imageDrawableId The resource id of the image to show at the start of the text bubble.
      * @param isRoundBubble Whether the bubble should be round.
+     * @param inverseColor Whether the background and icon/text colors should be inverted.
      * @param isAccessibilityEnabled Whether accessibility mode is enabled. Used to determine bubble
      *         text and dismiss UX.
      */
     public TextBubble(Context context, View rootView, @StringRes int stringId,
             @StringRes int accessibilityStringId, boolean showArrow,
             RectProvider anchorRectProvider, @DrawableRes int imageDrawableId,
-            boolean isRoundBubble, boolean isAccessibilityEnabled) {
+            boolean isRoundBubble, boolean inverseColor, boolean isAccessibilityEnabled) {
         this(context, rootView, context.getString(stringId),
                 context.getString(accessibilityStringId), showArrow, anchorRectProvider,
                 AppCompatResources.getDrawable(context, imageDrawableId), isRoundBubble,
-                isAccessibilityEnabled);
+                inverseColor, isAccessibilityEnabled);
     }
 
     /**
@@ -234,17 +238,19 @@
      * @param imageDrawable The image to show at the start of the text bubble, or null if there
      *         should be no image.
      * @param isRoundBubble Whether the bubble should be round.
+     * @param inverseColor Whether the background and icon/text colors should be inverted.
      * @param isAccessibilityEnabled Whether accessibility mode is enabled. Used to determine bubble
      *         text and dismiss UX.
      */
     public TextBubble(Context context, View rootView, String contentString,
             String accessibilityString, boolean showArrow, RectProvider anchorRectProvider,
-            @Nullable Drawable imageDrawable, boolean isRoundBubble,
+            @Nullable Drawable imageDrawable, boolean isRoundBubble, boolean inverseColor,
             boolean isAccessibilityEnabled) {
         mContext = context;
         mString = contentString;
         mAccessibilityString = accessibilityString;
         mImageDrawable = imageDrawable;
+        mInverseColor = inverseColor;
         mIsAccessibilityEnabled = isAccessibilityEnabled;
 
         mBubbleDrawable = new ArrowBubbleDrawable(context, isRoundBubble);
@@ -273,8 +279,13 @@
         if (mIsAccessibilityEnabled) setDismissOnTouchInteraction(true);
 
         // Set predefined styles for the TextBubble.
-        mBubbleDrawable.setBubbleColor(ApiCompatibilityUtils.getColor(
-                mContext.getResources(), R.color.default_control_color_active));
+        if (inverseColor) {
+            mBubbleDrawable.setBubbleColor(ApiCompatibilityUtils.getColor(
+                    mContext.getResources(), R.color.default_bg_color));
+        } else {
+            mBubbleDrawable.setBubbleColor(ApiCompatibilityUtils.getColor(
+                    mContext.getResources(), R.color.default_control_color_active));
+        }
     }
 
     /** Shows the bubble. Will have no effect if the bubble is already showing. */
@@ -406,7 +417,12 @@
         }
         View view =
                 LayoutInflater.from(mContext).inflate(R.layout.textbubble_text_with_image, null);
-        ((ImageView) view.findViewById(R.id.image)).setImageDrawable(mImageDrawable);
+        ImageView imageView = view.findViewById(R.id.image);
+        imageView.setImageDrawable(mImageDrawable);
+        if (mInverseColor) {
+            imageView.setColorFilter(ApiCompatibilityUtils.getColor(
+                    mContext.getResources(), R.color.default_control_color_active));
+        }
         setText(view.findViewById(R.id.message));
         return view;
     }
@@ -416,5 +432,9 @@
      */
     private void setText(TextView view) {
         view.setText(mIsAccessibilityEnabled ? mAccessibilityString : mString);
+        if (mInverseColor) {
+            ApiCompatibilityUtils.setTextAppearance(
+                    view, R.style.TextAppearance_TextMediumThick_Blue);
+        }
     }
 }
diff --git a/components/embedder_support/android/view/content_view_render_view.cc b/components/embedder_support/android/view/content_view_render_view.cc
index 42f0e076..def811d 100644
--- a/components/embedder_support/android/view/content_view_render_view.cc
+++ b/components/embedder_support/android/view/content_view_render_view.cc
@@ -85,6 +85,12 @@
 
 void ContentViewRenderView::SurfaceDestroyed(JNIEnv* env,
                                              const JavaParamRef<jobject>& obj) {
+  // When we switch from Chrome to other app we can't detach child surface
+  // controls because it leads to a visible hole: b/157439199. To avoid this we
+  // don't detach surfaces if the surface is going to be destroyed, they will be
+  // detached and freed by OS.
+  compositor_->PreserveChildSurfaceControls();
+
   compositor_->SetSurface(nullptr, false);
   current_surface_format_ = 0;
 }
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index 85edcd3..a7b1ad4 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -166,6 +166,19 @@
   return 0;
 }
 
+int SystemUiBehavior(const display::Display& display) {
+  auto* shelf_layout_manager = GetShelfLayoutManagerForDisplay(display);
+  switch (shelf_layout_manager->auto_hide_behavior()) {
+    case ash::ShelfAutoHideBehavior::kNever:
+      return ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_VISIBLE;
+    case ash::ShelfAutoHideBehavior::kAlways:
+    case ash::ShelfAutoHideBehavior::kAlwaysHidden:
+      return ZCR_REMOTE_SURFACE_V1_SYSTEMUI_VISIBILITY_STATE_AUTOHIDE_NON_STICKY;
+  }
+  NOTREACHED() << "Got unexpected shelf visibility behavior.";
+  return 0;
+}
+
 int Component(uint32_t direction) {
   switch (direction) {
     case ZCR_REMOTE_SURFACE_V1_RESIZE_DIRECTION_NONE:
@@ -847,7 +860,7 @@
         resource_, stable_insets_in_pixel.left(), stable_insets_in_pixel.top(),
         stable_insets_in_pixel.right(), stable_insets_in_pixel.bottom());
 
-    int systemui_visibility = SystemUiVisibility(display);
+    int systemui_visibility = SystemUiBehavior(display);
     zcr_remote_output_v1_send_systemui_visibility(resource_,
                                                   systemui_visibility);
 
diff --git a/components/flags_ui/feature_entry.cc b/components/flags_ui/feature_entry.cc
index a995452..1ffe781 100644
--- a/components/flags_ui/feature_entry.cc
+++ b/components/flags_ui/feature_entry.cc
@@ -16,6 +16,9 @@
 // also need to update the html file.
 const char kMultiSeparatorChar = '@';
 
+// These descriptions are translated for display in Chrome Labs. If these
+// strings are changed the translated strings in Chrome Labs must also be
+// changed (IDS_CHROMELABS_XXX).
 const char kGenericExperimentChoiceDefault[] = "Default";
 const char kGenericExperimentChoiceEnabled[] = "Enabled";
 const char kGenericExperimentChoiceDisabled[] = "Disabled";
@@ -69,6 +72,10 @@
          base::NumberToString(index);
 }
 
+// The order in which these descriptions are returned is the same in the
+// LabsComboboxModel::GetItemAt(..) (in the chrome_labs_item_view.cc file) for
+// the translated version of these strings. If there are changes to this, the
+// same changes must be made in LabsComboboxModel
 std::u16string FeatureEntry::DescriptionForOption(int index) const {
   DCHECK(type == FeatureEntry::MULTI_VALUE ||
          type == FeatureEntry::ENABLE_DISABLE_VALUE ||
diff --git a/components/full_restore/full_restore_info.h b/components/full_restore/full_restore_info.h
index 3bc258d..b685fd5 100644
--- a/components/full_restore/full_restore_info.h
+++ b/components/full_restore/full_restore_info.h
@@ -35,11 +35,20 @@
     virtual void OnRestoreFlagChanged(const AccountId& account_id,
                                       bool should_restore) {}
 
-    // Notifies when |window| is ready to be restored, after we have the app
-    // launch information, e.g. a task id for an ARC app.
+    // Notifies when |window| is ready to save the window info.
+    //
+    // When |window| is created, we might not have the app launch info yet. For
+    // example, if the ARC task is not created, we don't have the launch info.
+    // When the task is created, OnAppLaunched is called to notify observers to
+    // save the window info.
     virtual void OnAppLaunched(aura::Window* window) {}
 
-    // Notifies when |window| has been initialized.
+    // If |window| is restored from the full restore file, notifies observers to
+    // restore |window|, when |window| has been initialized.
+    //
+    // For ARC app windows, when |window| is initialized, the task might not be
+    // created yet, so we don't have the window info, |window| might be parent
+    // to a hidden container based on the property kParentToHiddenContainerKey.
     virtual void OnWindowInitialized(aura::Window* window) {}
 
    protected:
diff --git a/components/full_restore/full_restore_read_handler.cc b/components/full_restore/full_restore_read_handler.cc
index c62344c9..14fa02e 100644
--- a/components/full_restore/full_restore_read_handler.cc
+++ b/components/full_restore/full_restore_read_handler.cc
@@ -13,6 +13,7 @@
 #include "base/task/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/full_restore/full_restore_file_handler.h"
+#include "components/full_restore/full_restore_info.h"
 #include "components/full_restore/restore_data.h"
 #include "components/full_restore/window_info.h"
 #include "components/sessions/core/session_id.h"
@@ -43,6 +44,7 @@
       return;
 
     observed_windows_.AddObservation(window);
+    FullRestoreInfo::GetInstance()->OnWindowInitialized(window);
     return;
   }
 
@@ -51,6 +53,7 @@
   }
 
   observed_windows_.AddObservation(window);
+  FullRestoreInfo::GetInstance()->OnWindowInitialized(window);
 }
 
 void FullRestoreReadHandler::OnWindowDestroyed(aura::Window* window) {
@@ -118,19 +121,37 @@
   it->second->RemoveApp(app_id);
 }
 
-std::unique_ptr<WindowInfo> FullRestoreReadHandler::GetWindowInfo(
+void FullRestoreReadHandler::RemoveAppRestoreData(
+    const base::FilePath& profile_path,
+    const std::string& app_id,
     int32_t restore_window_id) {
-  // TODO(crbug.com/1146900): Handle ARC app windows.
+  auto it = profile_path_to_restore_data_.find(profile_path);
+  if (it == profile_path_to_restore_data_.end())
+    return;
 
+  it->second->RemoveAppRestoreData(app_id, restore_window_id);
+}
+
+bool FullRestoreReadHandler::HasWindowInfo(int32_t restore_window_id) {
   if (!SessionID::IsValidValue(restore_window_id))
-    return nullptr;
+    return false;
 
   auto it = window_id_to_app_restore_info_.find(restore_window_id);
   if (it == window_id_to_app_restore_info_.end())
+    return false;
+
+  return true;
+}
+
+std::unique_ptr<WindowInfo> FullRestoreReadHandler::GetWindowInfo(
+    const base::FilePath& profile_path,
+    const std::string& app_id,
+    int32_t restore_window_id) {
+  auto it = profile_path_to_restore_data_.find(profile_path);
+  if (it == profile_path_to_restore_data_.end())
     return nullptr;
 
-  return profile_path_to_restore_data_[it->second.first]->GetWindowInfo(
-      it->second.second, restore_window_id);
+  return it->second->GetWindowInfo(app_id, restore_window_id);
 }
 
 std::unique_ptr<WindowInfo> FullRestoreReadHandler::GetWindowInfo(
@@ -212,6 +233,20 @@
   arc_session_id_to_window_id_[arc_session_id] = window_id;
 }
 
+std::unique_ptr<WindowInfo> FullRestoreReadHandler::GetWindowInfo(
+    int32_t restore_window_id) {
+  if (!SessionID::IsValidValue(restore_window_id))
+    return nullptr;
+
+  auto it = window_id_to_app_restore_info_.find(restore_window_id);
+  if (it == window_id_to_app_restore_info_.end())
+    return nullptr;
+
+  const base::FilePath& profile_path = it->second.first;
+  const std::string& app_id = it->second.second;
+  return GetWindowInfo(profile_path, app_id, restore_window_id);
+}
+
 void FullRestoreReadHandler::OnGetRestoreData(
     const base::FilePath& profile_path,
     Callback callback,
@@ -232,13 +267,14 @@
   std::move(callback).Run(std::move(restore_data));
 }
 
-void FullRestoreReadHandler::RemoveAppRestoreData(int window_id) {
+void FullRestoreReadHandler::RemoveAppRestoreData(int32_t window_id) {
   auto it = window_id_to_app_restore_info_.find(window_id);
   if (it == window_id_to_app_restore_info_.end())
     return;
 
-  profile_path_to_restore_data_[it->second.first]->RemoveAppRestoreData(
-      it->second.second, window_id);
+  const base::FilePath& profile_path = it->second.first;
+  const std::string& app_id = it->second.second;
+  RemoveAppRestoreData(profile_path, app_id, window_id);
 
   window_id_to_app_restore_info_.erase(it);
 }
diff --git a/components/full_restore/full_restore_read_handler.h b/components/full_restore/full_restore_read_handler.h
index 3e7abcb..5ec2be1d 100644
--- a/components/full_restore/full_restore_read_handler.h
+++ b/components/full_restore/full_restore_read_handler.h
@@ -73,8 +73,24 @@
   // from |profile_path_to_restore_data_| for |profile_path| .
   void RemoveApp(const base::FilePath& profile_path, const std::string& app_id);
 
-  // Gets the window information for |restore_window_id| or |window|.
-  std::unique_ptr<WindowInfo> GetWindowInfo(int32_t restore_window_id);
+  // Removes AppRestoreData from |profile_path| for |app_id| and
+  // |restore_window_id|.
+  void RemoveAppRestoreData(const base::FilePath& profile_path,
+                            const std::string& app_id,
+                            int32_t restore_window_id);
+
+  // Returns true if there is a window info for |restore_window_id| from the
+  // full restore file. Otherwise, returns false. This interface can't be used
+  // for Arc app windows.
+  bool HasWindowInfo(int32_t restore_window_id);
+
+  // Gets the window information from |profile_path| for |app_id| and
+  // |restore_window_id|.
+  std::unique_ptr<WindowInfo> GetWindowInfo(const base::FilePath& profile_path,
+                                            const std::string& app_id,
+                                            int32_t restore_window_id);
+
+  // Gets the window information for |window|.
   std::unique_ptr<WindowInfo> GetWindowInfo(aura::Window* window);
 
   // Fetches the restore id for the window from RestoreData for the given
@@ -110,6 +126,9 @@
   }
 
  private:
+  // Gets the window information for |restore_window_id|.
+  std::unique_ptr<WindowInfo> GetWindowInfo(int32_t restore_window_id);
+
   // Invoked when reading the restore data from |profile_path| is finished, and
   // calls |callback| to notify that the reading operation is done.
   void OnGetRestoreData(const base::FilePath& profile_path,
@@ -117,7 +136,7 @@
                         std::unique_ptr<RestoreData>);
 
   // Removes AppRestoreData for |restore_window_id|.
-  void RemoveAppRestoreData(int restore_window_id);
+  void RemoveAppRestoreData(int32_t restore_window_id);
 
   // The current active user profile path.
   base::FilePath active_profile_path_;
@@ -127,9 +146,9 @@
       profile_path_to_restore_data_;
 
   // The map from the window id to the full restore file path and the
-  // app id. The window id id is saved in the window property
+  // app id. The window id is saved in the window property
   // |kRestoreWindowIdKey|. This map is used to find the file path and the app
-  // id when get the window info.
+  // id when get the window info. This map is not used for ARC app windows.
   std::map<int32_t, std::pair<base::FilePath, std::string>>
       window_id_to_app_restore_info_;
 
diff --git a/components/full_restore/full_restore_save_handler.cc b/components/full_restore/full_restore_save_handler.cc
index d308965..4f313b6 100644
--- a/components/full_restore/full_restore_save_handler.cc
+++ b/components/full_restore/full_restore_save_handler.cc
@@ -63,7 +63,6 @@
       static_cast<int>(ash::AppType::ARC_APP)) {
     task_id_to_app_window_info_[window_id].window = window;
     observed_windows_.AddObservation(window);
-    FullRestoreInfo::GetInstance()->OnWindowInitialized(window);
 
     // If the task id has an app id, OnTaskCreated has been invoked, and the app
     // launch info has been saved, so OnAppLaunched can be called to save the
@@ -78,7 +77,6 @@
     return;
 
   observed_windows_.AddObservation(window);
-  FullRestoreInfo::GetInstance()->OnWindowInitialized(window);
 
   std::string* app_id_str = window->GetProperty(::full_restore::kAppIdKey);
   AppLaunchInfoPtr app_launch_info;
@@ -125,7 +123,7 @@
       // The window could be recreated, so only remove the window info. When the
       // task is destroyed, the full restore data can be removed.
       it->second.window = nullptr;
-      RemoveWindowInfo(it->second.app_id, window_id);
+      RemoveWindowInfo(primary_profile_path_, it->second.app_id, window_id);
     }
     return;
   }
@@ -249,6 +247,49 @@
                          weak_factory_.GetWeakPtr(), profile_path));
 }
 
+void FullRestoreSaveHandler::AddAppLaunchInfo(
+    const base::FilePath& profile_path,
+    AppLaunchInfoPtr app_launch_info) {
+  if (profile_path.empty() || !app_launch_info ||
+      !app_launch_info->window_id.has_value()) {
+    return;
+  }
+
+  if (!app_launch_info->arc_session_id.has_value()) {
+    // If |app_launch_info| is not an ARC app launch info, add to
+    // |window_id_to_app_restore_info_|.
+    window_id_to_app_restore_info_[app_launch_info->window_id.value()] =
+        std::make_pair(profile_path, app_launch_info->app_id);
+  }
+
+  // Each user should have one full restore file saving the restore data in the
+  // profile directory |profile_path|. So |app_launch_info| is saved to the
+  // restore data for the user with |profile_path|.
+  profile_path_to_restore_data_[profile_path].AddAppLaunchInfo(
+      std::move(app_launch_info));
+
+  pending_save_profile_paths_.insert(profile_path);
+
+  MaybeStartSaveTimer();
+}
+
+void FullRestoreSaveHandler::ModifyWindowInfo(
+    const base::FilePath& profile_path,
+    const std::string& app_id,
+    int32_t window_id,
+    const WindowInfo& window_info) {
+  auto it = profile_path_to_restore_data_.find(profile_path);
+  if (it == profile_path_to_restore_data_.end())
+    return;
+
+  profile_path_to_restore_data_[profile_path].ModifyWindowInfo(
+      app_id, window_id, window_info);
+
+  pending_save_profile_paths_.insert(profile_path);
+
+  MaybeStartSaveTimer();
+}
+
 void FullRestoreSaveHandler::RemoveApp(const base::FilePath& profile_path,
                                        const std::string& app_id) {
   auto it = profile_path_to_restore_data_.find(profile_path);
@@ -262,6 +303,36 @@
   MaybeStartSaveTimer();
 }
 
+void FullRestoreSaveHandler::RemoveAppRestoreData(
+    const base::FilePath& profile_path,
+    const std::string& app_id,
+    int window_id) {
+  auto it = profile_path_to_restore_data_.find(profile_path);
+  if (it == profile_path_to_restore_data_.end())
+    return;
+
+  it->second.RemoveAppRestoreData(app_id, window_id);
+
+  pending_save_profile_paths_.insert(profile_path);
+
+  MaybeStartSaveTimer();
+}
+
+void FullRestoreSaveHandler::RemoveWindowInfo(
+    const base::FilePath& profile_path,
+    const std::string& app_id,
+    int window_id) {
+  auto it = profile_path_to_restore_data_.find(profile_path);
+  if (it == profile_path_to_restore_data_.end())
+    return;
+
+  it->second.RemoveWindowInfo(app_id, window_id);
+
+  pending_save_profile_paths_.insert(profile_path);
+
+  MaybeStartSaveTimer();
+}
+
 int32_t FullRestoreSaveHandler::GetArcSessionId() {
   if (arc_session_id_ >= kArcSessionIdOffsetForRestoredLaunching) {
     LOG(WARNING) << "ARC session id is too large: " << arc_session_id_;
@@ -309,48 +380,15 @@
   return GetFileHandler(profile_path)->owning_task_runner();
 }
 
-void FullRestoreSaveHandler::AddAppLaunchInfo(
-    const base::FilePath& profile_path,
-    AppLaunchInfoPtr app_launch_info) {
-  if (profile_path.empty() || !app_launch_info ||
-      !app_launch_info->window_id.has_value()) {
-    return;
-  }
-
-  window_id_to_app_restore_info_[app_launch_info->window_id.value()] =
-      std::make_pair(profile_path, app_launch_info->app_id);
-
-  // Each user should have one full restore file saving the restore data in the
-  // profile directory |profile_path|. So |app_launch_info| is saved to the
-  // restore data for the user with |profile_path|.
-  profile_path_to_restore_data_[profile_path].AddAppLaunchInfo(
-      std::move(app_launch_info));
-
-  pending_save_profile_paths_.insert(profile_path);
-
-  MaybeStartSaveTimer();
-}
-
 void FullRestoreSaveHandler::ModifyWindowInfo(int window_id,
                                               const WindowInfo& window_info) {
   auto it = window_id_to_app_restore_info_.find(window_id);
   if (it == window_id_to_app_restore_info_.end())
     return;
 
-  const auto& profile_path = it->second.first;
-  const auto& app_id = it->second.second;
-  auto restore_data_it = profile_path_to_restore_data_.find(profile_path);
-  if (restore_data_it == profile_path_to_restore_data_.end() ||
-      !restore_data_it->second.HasAppRestoreData(app_id, window_id)) {
-    return;
-  }
-
-  profile_path_to_restore_data_[profile_path].ModifyWindowInfo(
-      app_id, window_id, window_info);
-
-  pending_save_profile_paths_.insert(profile_path);
-
-  MaybeStartSaveTimer();
+  const base::FilePath& profile_path = it->second.first;
+  const std::string& app_id = it->second.second;
+  ModifyWindowInfo(profile_path, app_id, window_id, window_info);
 }
 
 void FullRestoreSaveHandler::RemoveAppRestoreData(int window_id) {
@@ -358,26 +396,12 @@
   if (it == window_id_to_app_restore_info_.end())
     return;
 
-  profile_path_to_restore_data_[it->second.first].RemoveAppRestoreData(
-      it->second.second, window_id);
+  const base::FilePath& profile_path = it->second.first;
+  const std::string& app_id = it->second.second;
 
-  pending_save_profile_paths_.insert(it->second.first);
+  RemoveAppRestoreData(profile_path, app_id, window_id);
 
   window_id_to_app_restore_info_.erase(it);
-
-  MaybeStartSaveTimer();
-}
-
-void FullRestoreSaveHandler::RemoveWindowInfo(const std::string& app_id,
-                                              int window_id) {
-  if (app_id.empty())
-    return;
-
-  auto it = profile_path_to_restore_data_.find(primary_profile_path_);
-  if (it == profile_path_to_restore_data_.end())
-    return;
-
-  it->second.RemoveWindowInfo(app_id, window_id);
 }
 
 }  // namespace full_restore
diff --git a/components/full_restore/full_restore_save_handler.h b/components/full_restore/full_restore_save_handler.h
index d27f863..4806cef 100644
--- a/components/full_restore/full_restore_save_handler.h
+++ b/components/full_restore/full_restore_save_handler.h
@@ -40,6 +40,8 @@
     : public aura::EnvObserver,
       public aura::WindowObserver {
  public:
+  using AppLaunchInfoPtr = std::unique_ptr<AppLaunchInfo>;
+
   static FullRestoreSaveHandler* GetInstance();
 
   FullRestoreSaveHandler();
@@ -77,10 +79,32 @@
   // data.
   void Flush(const base::FilePath& profile_path);
 
+  // Saves |app_launch_info| to |profile_path_to_file_handler_| for
+  // |profile_path| which will be written to the full restore file, if
+  // |app_launch_info| has a window_id.
+  void AddAppLaunchInfo(const base::FilePath& profile_path,
+                        AppLaunchInfoPtr app_launch_info);
+
+  // Saves |window_info| to |profile_path| for |app_id| and |window_id|.
+  void ModifyWindowInfo(const base::FilePath& profile_path,
+                        const std::string& app_id,
+                        int32_t window_id,
+                        const WindowInfo& window_info);
+
   // Removes app launching and app windows for an app with the given |app_id|
   // from |file_path_to_restore_data_| for |profile_path| .
   void RemoveApp(const base::FilePath& profile_path, const std::string& app_id);
 
+  // Removes AppRestoreData from |profile_path| for |app_id| and |window_id|.
+  void RemoveAppRestoreData(const base::FilePath& profile_path,
+                            const std::string& app_id,
+                            int window_id);
+
+  // Removes WindowInfo from |profile_path| for |app_id| and |window_id|.
+  void RemoveWindowInfo(const base::FilePath& profile_path,
+                        const std::string& app_id,
+                        int window_id);
+
   // Generates the ARC session id (0 - 1,000,000,000) for ARC apps.
   int32_t GetArcSessionId();
 
@@ -92,8 +116,6 @@
     aura::Window* window;
   };
 
-  using AppLaunchInfoPtr = std::unique_ptr<AppLaunchInfo>;
-
   // Map from a profile path to AppLaunchInfos.
   using AppLaunchInfos = std::map<base::FilePath, std::list<AppLaunchInfoPtr>>;
 
@@ -111,21 +133,12 @@
   base::SequencedTaskRunner* BackendTaskRunner(
       const base::FilePath& profile_path);
 
-  // Saves |app_launch_info| to |profile_path_to_file_handler_| for
-  // |profile_path| which will be written to the full restore file, if
-  // |app_launch_info| has a window_id.
-  void AddAppLaunchInfo(const base::FilePath& profile_path,
-                        AppLaunchInfoPtr app_launch_info);
-
   // Saves |window_info| to |profile_path_to_file_handler_|.
   void ModifyWindowInfo(int window_id, const WindowInfo& window_info);
 
   // Removes AppRestoreData for |window_id|.
   void RemoveAppRestoreData(int window_id);
 
-  // Removes WindowInfo for |app_id| and |window_id|.
-  void RemoveWindowInfo(const std::string& app_id, int window_id);
-
   // Records whether there are new updates for saving between each saving delay.
   // |pending_save_profile_paths_| is cleared when Save is invoked.
   std::set<base::FilePath> pending_save_profile_paths_;
@@ -140,7 +153,8 @@
 
   // The map from the window id to the full restore file path and the app id.
   // The window id is saved in the window property. This map is used to find the
-  // file path and the app id when save the window info.
+  // file path and the app id for browser windows and Chrome app windows only
+  // when save the window info. This map can't be used for ARC app windows.
   std::map<int32_t, std::pair<base::FilePath, std::string>>
       window_id_to_app_restore_info_;
 
diff --git a/components/full_restore/full_restore_utils.cc b/components/full_restore/full_restore_utils.cc
index a63a4dd..48bbd64 100644
--- a/components/full_restore/full_restore_utils.cc
+++ b/components/full_restore/full_restore_utils.cc
@@ -38,14 +38,6 @@
   FullRestoreSaveHandler::GetInstance()->SaveWindowInfo(window_info);
 }
 
-std::unique_ptr<WindowInfo> GetWindowInfo(int32_t restore_window_id) {
-  if (!ash::features::IsFullRestoreEnabled())
-    return nullptr;
-
-  return FullRestoreReadHandler::GetInstance()->GetWindowInfo(
-      restore_window_id);
-}
-
 std::unique_ptr<WindowInfo> GetWindowInfo(aura::Window* window) {
   if (!ash::features::IsFullRestoreEnabled())
     return nullptr;
@@ -83,8 +75,8 @@
   if (!ash::features::IsFullRestoreEnabled())
     return false;
 
-  std::unique_ptr<WindowInfo> window_info = GetWindowInfo(restore_window_id);
-  return !!window_info;
+  return FullRestoreReadHandler::GetInstance()->HasWindowInfo(
+      restore_window_id);
 }
 
 void ModifyWidgetParams(int32_t restore_window_id,
diff --git a/components/full_restore/full_restore_utils.h b/components/full_restore/full_restore_utils.h
index 8957331..98290a13 100644
--- a/components/full_restore/full_restore_utils.h
+++ b/components/full_restore/full_restore_utils.h
@@ -70,9 +70,7 @@
 COMPONENT_EXPORT(FULL_RESTORE)
 void SaveWindowInfo(const WindowInfo& window_info);
 
-// Gets the window information from the full restore file.
-COMPONENT_EXPORT(FULL_RESTORE)
-std::unique_ptr<WindowInfo> GetWindowInfo(int32_t restore_window_id);
+// Gets the window information from the full restore file for |window|.
 COMPONENT_EXPORT(FULL_RESTORE)
 std::unique_ptr<WindowInfo> GetWindowInfo(aura::Window* window);
 
@@ -94,7 +92,8 @@
 void SetActiveProfilePath(const base::FilePath& profile_path);
 
 // Returns true if there is a window info for |restore_window_id| from the full
-// restore file. Otherwise, returns false.
+// restore file. Otherwise, returns false. This interface can't be used for Arc
+// app windows.
 COMPONENT_EXPORT(FULL_RESTORE)
 bool HasWindowInfo(int32_t restore_window_id);
 
diff --git a/components/language/core/browser/language_prefs.cc b/components/language/core/browser/language_prefs.cc
index fc220d8c..f05230cb 100644
--- a/components/language/core/browser/language_prefs.cc
+++ b/components/language/core/browser/language_prefs.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/containers/contains.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -33,6 +34,12 @@
   registry->RegisterStringPref(language::prefs::kAcceptLanguages,
                                l10n_util::GetStringUTF8(IDS_ACCEPT_LANGUAGES),
                                user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  registry->RegisterStringPref(language::prefs::kSelectedLanguages,
+                               std::string(),
+                               user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  registry->RegisterListPref(language::prefs::kForcedLanguages);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterStringPref(language::prefs::kPreferredLanguages,
                                kFallbackInputMethodLocale);
@@ -48,6 +55,17 @@
 
 LanguagePrefs::LanguagePrefs(PrefService* user_prefs) : prefs_(user_prefs) {
   ResetEmptyFluentLanguagesToDefault();
+  InitializeSelectedLanguagesPref();
+  UpdateAcceptLanguagesPref();
+  base::RepeatingClosure callback = base::BindRepeating(
+      &LanguagePrefs::UpdateAcceptLanguagesPref, base::Unretained(this));
+  pref_change_registrar_.Init(prefs_);
+  pref_change_registrar_.Add(language::prefs::kForcedLanguages, callback);
+  pref_change_registrar_.Add(language::prefs::kSelectedLanguages, callback);
+}
+
+LanguagePrefs::~LanguagePrefs() {
+  pref_change_registrar_.RemoveAll();
 }
 
 bool LanguagePrefs::IsFluent(base::StringPiece language) const {
@@ -117,14 +135,70 @@
                                  base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
 }
 
-void LanguagePrefs::SetAcceptLanguagesList(
+void LanguagePrefs::GetUserSelectedLanguagesList(
+    std::vector<std::string>* languages) const {
+  DCHECK(languages);
+  DCHECK(languages->empty());
+  const std::string& key = language::prefs::kSelectedLanguages;
+  *languages = base::SplitString(prefs_->GetString(key), ",",
+                                 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+}
+
+void LanguagePrefs::SetUserSelectedLanguagesList(
     const std::vector<std::string>& languages) {
   std::string languages_str = base::JoinString(languages, ",");
+  prefs_->SetString(language::prefs::kSelectedLanguages, languages_str);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   prefs_->SetString(language::prefs::kPreferredLanguages, languages_str);
 #endif
+}
 
-  prefs_->SetString(language::prefs::kAcceptLanguages, languages_str);
+void LanguagePrefs::GetDeduplicatedUserLanguages(
+    std::string* deduplicated_languages_string) {
+  std::vector<std::string> deduplicated_languages;
+  forced_languages_set_.clear();
+
+  // Add policy languages.
+  for (const auto& language :
+       *prefs_->GetList(language::prefs::kForcedLanguages)) {
+    if (forced_languages_set_.find(language.GetString()) ==
+        forced_languages_set_.end()) {
+      deduplicated_languages.emplace_back(language.GetString());
+      forced_languages_set_.insert(language.GetString());
+    }
+  }
+
+  // Add non-duplicate user-selected languages.
+  for (auto& language :
+       base::SplitString(prefs_->GetString(language::prefs::kSelectedLanguages),
+                         ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    if (forced_languages_set_.find(language) == forced_languages_set_.end())
+      deduplicated_languages.emplace_back(std::move(language));
+  }
+  *deduplicated_languages_string =
+      base::JoinString(deduplicated_languages, ",");
+}
+
+void LanguagePrefs::UpdateAcceptLanguagesPref() {
+  std::string deduplicated_languages_string;
+  GetDeduplicatedUserLanguages(&deduplicated_languages_string);
+  if (deduplicated_languages_string !=
+      prefs_->GetString(language::prefs::kAcceptLanguages))
+    prefs_->SetString(language::prefs::kAcceptLanguages,
+                      deduplicated_languages_string);
+}
+
+bool LanguagePrefs::IsForcedLanguage(const std::string& language) {
+  return forced_languages_set_.find(language) != forced_languages_set_.end();
+}
+
+void LanguagePrefs::InitializeSelectedLanguagesPref() {
+  // Initializes user-selected languages if they're empty.
+  // This is important so that previously saved languages aren't overwritten.
+  if (prefs_->GetString(language::prefs::kSelectedLanguages).empty()) {
+    prefs_->SetString(language::prefs::kSelectedLanguages,
+                      prefs_->GetString(language::prefs::kAcceptLanguages));
+  }
 }
 
 // static
@@ -176,6 +250,7 @@
 
 void ResetLanguagePrefs(PrefService* prefs) {
   prefs->ClearPref(language::prefs::kAcceptLanguages);
+  prefs->ClearPref(language::prefs::kSelectedLanguages);
   prefs->ClearPref(language::prefs::kFluentLanguages);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   prefs->ClearPref(language::prefs::kPreferredLanguages);
diff --git a/components/language/core/browser/language_prefs.h b/components/language/core/browser/language_prefs.h
index f6553f64..28c8954 100644
--- a/components/language/core/browser/language_prefs.h
+++ b/components/language/core/browser/language_prefs.h
@@ -5,10 +5,12 @@
 #ifndef COMPONENTS_LANGUAGE_CORE_BROWSER_LANGUAGE_PREFS_H_
 #define COMPONENTS_LANGUAGE_CORE_BROWSER_LANGUAGE_PREFS_H_
 
+#include <set>
 #include <string>
 
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
+#include "components/prefs/pref_change_registrar.h"
 
 class PrefService;
 
@@ -30,7 +32,8 @@
  public:
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  LanguagePrefs(PrefService* user_prefs);
+  explicit LanguagePrefs(PrefService* user_prefs);
+  ~LanguagePrefs();
 
   // Return true iff the user is fluent in the given |language|.
   bool IsFluent(base::StringPiece language) const;
@@ -47,17 +50,41 @@
   std::vector<std::string> GetFluentLanguages() const;
   // If the list of fluent languages is empty, reset it to defaults.
   void ResetEmptyFluentLanguagesToDefault();
-  // Gets the language list of the language settings. Language settings list
-  // have the Chrome internal format.
+  // Gets the language settings list containing combination of policy-forced and
+  // user-selected languages. Language settings list follows the Chrome internal
+  // format.
   void GetAcceptLanguagesList(std::vector<std::string>* languages) const;
-  // Updates the language list of the language settings. Languages are expected
-  // to be in the Chrome internal format.
-  void SetAcceptLanguagesList(const std::vector<std::string>& languages);
+  // Gets the user-selected language settings list. Languages are expected to be
+  // in the Chrome internal format.
+  void GetUserSelectedLanguagesList(std::vector<std::string>* languages) const;
+  // Updates the user-selected language settings list. Languages are expected to
+  // be in the Chrome internal format.
+  void SetUserSelectedLanguagesList(const std::vector<std::string>& languages);
+  // Returns true if the target language is forced through policy.
+  bool IsForcedLanguage(const std::string& language);
 
  private:
+  // Updates the language list containing combination of policy-forced and
+  // user-selected languages.
+  void GetDeduplicatedUserLanguages(std::string* deduplicated_languages_string);
+  // Updates the pref corresponding to the language list containing combination
+  // of policy-forced and user-selected languages.
+  // Since languages may be removed from the policy while the browser is off,
+  // having an additional policy solely for user-selected languages allows
+  // Chrome to clear any removed policy languages from the accept languages pref
+  // while retaining all user-selected languages.
+  void UpdateAcceptLanguagesPref();
+  // Initializes the user selected language pref to ensure backwards
+  // compatibility.
+  void InitializeSelectedLanguagesPref();
+
   size_t NumFluentLanguages() const;
 
+  // Used for deduplication and reordering of languages.
+  std::set<std::string> forced_languages_set_;
+
   PrefService* prefs_;  // Weak.
+  PrefChangeRegistrar pref_change_registrar_;
 
   DISALLOW_COPY_AND_ASSIGN(LanguagePrefs);
 };
diff --git a/components/language/core/browser/language_prefs_test_util.cc b/components/language/core/browser/language_prefs_test_util.cc
index e38a3570..5dc01bd0 100644
--- a/components/language/core/browser/language_prefs_test_util.cc
+++ b/components/language/core/browser/language_prefs_test_util.cc
@@ -14,44 +14,71 @@
 namespace language {
 namespace test {
 
-AcceptLanguagesTester::AcceptLanguagesTester(PrefService* user_prefs)
+LanguagePrefTester::LanguagePrefTester(PrefService* user_prefs)
     : prefs_(user_prefs) {}
 
-void AcceptLanguagesTester::ExpectLanguagePrefs(
+void LanguagePrefTester::ExpectPref(
+    const std::string& pref_name,
     const std::string& expected_prefs,
     const std::string& expected_prefs_chromeos) const {
   if (expected_prefs.empty()) {
-    EXPECT_TRUE(prefs_->GetString(language::prefs::kAcceptLanguages).empty());
+    EXPECT_TRUE(prefs_->GetString(pref_name).empty());
   } else {
-    EXPECT_EQ(expected_prefs,
-              prefs_->GetString(language::prefs::kAcceptLanguages));
+    EXPECT_EQ(expected_prefs, prefs_->GetString(pref_name));
   }
+}
+
+void LanguagePrefTester::ExpectAcceptLanguagePrefs(
+    const std::string& expected_prefs,
+    const std::string& expected_prefs_chromeos) const {
+  ExpectPref(language::prefs::kAcceptLanguages, expected_prefs,
+             expected_prefs_chromeos);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (expected_prefs_chromeos.empty()) {
-    EXPECT_TRUE(
-        prefs_->GetString(language::prefs::kPreferredLanguages).empty());
-  } else {
-    EXPECT_EQ(expected_prefs_chromeos,
-              prefs_->GetString(language::prefs::kPreferredLanguages));
-  }
+  ExpectPref(language::prefs::kPreferredLanguages, expected_prefs,
+             expected_prefs_chromeos);
 #endif
 }
 
 // Similar to function above: this one expects both ChromeOS and other
 // platforms to have the same value of language prefs.
-void AcceptLanguagesTester::ExpectLanguagePrefs(
+void LanguagePrefTester::ExpectAcceptLanguagePrefs(
     const std::string& expected_prefs) const {
-  ExpectLanguagePrefs(expected_prefs, expected_prefs);
+  ExpectAcceptLanguagePrefs(expected_prefs, expected_prefs);
 }
 
-void AcceptLanguagesTester::SetLanguagePrefs(
+void LanguagePrefTester::ExpectSelectedLanguagePrefs(
+    const std::string& expected_prefs,
+    const std::string& expected_prefs_chromeos) const {
+  ExpectPref(language::prefs::kSelectedLanguages, expected_prefs,
+             expected_prefs_chromeos);
+}
+
+// Similar to function above: this one expects both ChromeOS and other
+// platforms to have the same value of language prefs.
+void LanguagePrefTester::ExpectSelectedLanguagePrefs(
+    const std::string& expected_prefs) const {
+  ExpectSelectedLanguagePrefs(expected_prefs, expected_prefs);
+}
+
+void LanguagePrefTester::SetLanguagePrefs(
     const std::vector<std::string>& languages) {
   std::string languages_str = base::JoinString(languages, ",");
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   prefs_->SetString(language::prefs::kPreferredLanguages, languages_str);
 #endif
+  prefs_->SetString(language::prefs::kSelectedLanguages, languages_str);
+}
 
-  prefs_->SetString(language::prefs::kAcceptLanguages, languages_str);
+void LanguagePrefTester::SetForcedLanguagePrefs(
+    std::vector<std::string>&& languages) {
+  base::Value::ListStorage languages_list;
+
+  for (std::string language : languages) {
+    languages_list.push_back(base::Value(std::move(language)));
+  }
+
+  prefs_->Set(language::prefs::kForcedLanguages,
+              base::Value(std::move(languages_list)));
 }
 
 }  // namespace test
diff --git a/components/language/core/browser/language_prefs_test_util.h b/components/language/core/browser/language_prefs_test_util.h
index 12e4cbe..77b7d6e1 100644
--- a/components/language/core/browser/language_prefs_test_util.h
+++ b/components/language/core/browser/language_prefs_test_util.h
@@ -16,21 +16,43 @@
 namespace test {
 
 // Helper class for testing Accept Languages.
-class AcceptLanguagesTester {
+class LanguagePrefTester {
  public:
-  explicit AcceptLanguagesTester(PrefService* user_prefs);
+  explicit LanguagePrefTester(PrefService* user_prefs);
 
-  // Checks that the provided strings are equivalent to the content language
-  // prefs. Chrome OS uses a different pref, so we need to handle it separately.
-  void ExpectLanguagePrefs(const std::string& expected_prefs,
-                           const std::string& expected_prefs_chromeos) const;
+  // Checks that the provided strings are equivalent to the language pref of
+  // interest.
+  void ExpectPref(const std::string& pref_name,
+                  const std::string& expected_prefs,
+                  const std::string& expected_prefs_chromeos) const;
+
+  // Checks that the provided strings are equivalent to the accept languages
+  // pref. Chrome OS uses a different pref, so we need to handle it separately.
+  void ExpectAcceptLanguagePrefs(
+      const std::string& expected_prefs,
+      const std::string& expected_prefs_chromeos) const;
 
   // Similar to function above: this one expects both ChromeOS and other
   // platforms to have the same value of language prefs.
-  void ExpectLanguagePrefs(const std::string& expected_prefs) const;
+  void ExpectAcceptLanguagePrefs(const std::string& expected_prefs) const;
 
+  // Checks that the provided strings are equivalent to the selected languages
+  // pref.
+  void ExpectSelectedLanguagePrefs(
+      const std::string& expected_prefs,
+      const std::string& expected_prefs_chromeos) const;
+
+  // Similar to function above: this one expects both ChromeOS and other
+  // platforms to have the same value of language prefs.
+  void ExpectSelectedLanguagePrefs(const std::string& expected_prefs) const;
+
+  // Sets the contents of the selected language pref. Chrome OS uses a different
+  // pref so it is handled separately.
   void SetLanguagePrefs(const std::vector<std::string>& languages);
 
+  // Sets the contents of the forced language pref.
+  void SetForcedLanguagePrefs(std::vector<std::string>&& languages);
+
  private:
   PrefService* prefs_;
 };
diff --git a/components/language/core/browser/language_prefs_unittest.cc b/components/language/core/browser/language_prefs_unittest.cc
index f60dd549..882b0dbf 100644
--- a/components/language/core/browser/language_prefs_unittest.cc
+++ b/components/language/core/browser/language_prefs_unittest.cc
@@ -186,32 +186,103 @@
 }
 
 TEST_F(LanguagePrefsTest, UpdateLanguageList) {
-  language::test::AcceptLanguagesTester content_languages_tester =
-      language::test::AcceptLanguagesTester(prefs_.get());
+  language::test::LanguagePrefTester content_languages_tester =
+      language::test::LanguagePrefTester(prefs_.get());
   // Empty update.
   std::vector<std::string> languages;
-  language_prefs_->SetAcceptLanguagesList(languages);
-  content_languages_tester.ExpectLanguagePrefs("");
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("");
 
   // One language.
   languages = {"en"};
-  language_prefs_->SetAcceptLanguagesList(languages);
-  content_languages_tester.ExpectLanguagePrefs("en");
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("en");
 
   // More than one language.
   languages = {"en", "ja", "it"};
-  language_prefs_->SetAcceptLanguagesList(languages);
-  content_languages_tester.ExpectLanguagePrefs("en,ja,it");
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,ja,it");
 
   // Locale-specific codes.
   // The list is exanded by adding the base languagese.
   languages = {"en-US", "ja", "en-CA", "fr-CA"};
-  language_prefs_->SetAcceptLanguagesList(languages);
-  content_languages_tester.ExpectLanguagePrefs("en-US,ja,en-CA,fr-CA");
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("en-US,ja,en-CA,fr-CA");
 
   // List already expanded.
   languages = {"en-US", "en", "fr", "fr-CA"};
-  language_prefs_->SetAcceptLanguagesList(languages);
-  content_languages_tester.ExpectLanguagePrefs("en-US,en,fr,fr-CA");
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("en-US,en,fr,fr-CA");
+}
+
+TEST_F(LanguagePrefsTest, UpdateForcedLanguageList) {
+  // Only test policy-forced languages on non-Chrome OS platforms.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  GTEST_SKIP();
+#else
+  language::test::LanguagePrefTester content_languages_tester =
+      language::test::LanguagePrefTester(prefs_.get());
+  // Empty update.
+  std::vector<std::string> languages;
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.ExpectAcceptLanguagePrefs("");
+
+  // Forced languages with no duplicates.
+  languages = {"fr"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,fr");
+  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref
+
+  // Forced languages with some duplicates.
+  languages = {"en-US", "en", "fr", "fr-CA"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en-US,en,fr,fr-CA");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,en-US,fr,fr-CA");
+  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref
+
+  // Forced languages with full duplicates.
+  languages = {"en", "es", "fr"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"en", "es", "fr"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,es,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,es,fr");
+  content_languages_tester.SetForcedLanguagePrefs({});  // Reset pref
+
+  // Add then remove forced languages with no duplicates.
+  languages = {"en", "fr"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"it"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("it,en,fr");
+  // Remove forced languages
+  content_languages_tester.SetForcedLanguagePrefs({});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
+
+  // Add then remove forced languages with some duplicates.
+  languages = {"en", "fr"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"en", "it"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,it,fr");
+  // Remove forced languages
+  content_languages_tester.SetForcedLanguagePrefs({});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
+
+  // Add then remove forced languages with full duplicates.
+  languages = {"en", "fr"};
+  language_prefs_->SetUserSelectedLanguagesList(languages);
+  content_languages_tester.SetForcedLanguagePrefs({"en", "fr"});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
+  // Remove forced languages
+  content_languages_tester.SetForcedLanguagePrefs({});
+  content_languages_tester.ExpectSelectedLanguagePrefs("en,fr");
+  content_languages_tester.ExpectAcceptLanguagePrefs("en,fr");
+#endif
 }
 }  // namespace language
diff --git a/components/language/core/browser/pref_names.cc b/components/language/core/browser/pref_names.cc
index 3a5a0cc..d30b2f2 100644
--- a/components/language/core/browser/pref_names.cc
+++ b/components/language/core/browser/pref_names.cc
@@ -10,9 +10,17 @@
 namespace prefs {
 
 // The value to use for Accept-Languages HTTP header when making an HTTP
-// request.
+// request. This should not be set directly as it is a combination of
+// kSelectedLanguages and kForcedLanguages. To update the list of preferred
+// languages, set kSelectedLanguages and this pref will update automatically.
 const char kAcceptLanguages[] = "intl.accept_languages";
 
+// List which contains the user-selected languages.
+const char kSelectedLanguages[] = "intl.selected_languages";
+
+// List which contains the policy-forced languages.
+const char kForcedLanguages[] = "intl.forced_languages";
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // A string pref (comma-separated list) set to the preferred language IDs
 // (ex. "en-US,fr,ko").
diff --git a/components/language/core/browser/pref_names.h b/components/language/core/browser/pref_names.h
index 7f54fe7..66dc95bd 100644
--- a/components/language/core/browser/pref_names.h
+++ b/components/language/core/browser/pref_names.h
@@ -12,6 +12,10 @@
 
 extern const char kAcceptLanguages[];
 
+extern const char kSelectedLanguages[];
+
+extern const char kForcedLanguages[];
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kPreferredLanguages[];
 extern const char kPreferredLanguagesSyncable[];
diff --git a/components/metrics/structured/persistent_proto.cc b/components/metrics/structured/persistent_proto.cc
index 7b63421..857e5f4 100644
--- a/components/metrics/structured/persistent_proto.cc
+++ b/components/metrics/structured/persistent_proto.cc
@@ -43,6 +43,10 @@
 
 template <class T>
 WriteStatus Write(const base::FilePath& filepath, const T* proto) {
+  DCHECK(proto);
+  if (!proto)
+    return WriteStatus::kWriteError;
+
   const auto directory = filepath.DirName();
   if (!base::DirectoryExists(directory))
     base::CreateDirectory(directory);
diff --git a/components/metrics/unsent_log_store_metrics_impl.cc b/components/metrics/unsent_log_store_metrics_impl.cc
index f6efa5a0..df36edb 100644
--- a/components/metrics/unsent_log_store_metrics_impl.cc
+++ b/components/metrics/unsent_log_store_metrics_impl.cc
@@ -8,13 +8,6 @@
 
 namespace metrics {
 
-void UnsentLogStoreMetricsImpl::RecordLogReadStatus(
-    UnsentLogStoreMetrics::LogReadStatus status) {
-  base::UmaHistogramEnumeration("PrefService.PersistentLogRecallProtobufs",
-                                status,
-                                UnsentLogStoreMetrics::END_RECALL_STATUS);
-}
-
 void UnsentLogStoreMetricsImpl::RecordCompressionRatio(size_t compressed_size,
                                                        size_t original_size) {
   base::UmaHistogramPercentageObsoleteDoNotUse(
diff --git a/components/metrics/unsent_log_store_metrics_impl.h b/components/metrics/unsent_log_store_metrics_impl.h
index ca548f5..e579e00 100644
--- a/components/metrics/unsent_log_store_metrics_impl.h
+++ b/components/metrics/unsent_log_store_metrics_impl.h
@@ -17,8 +17,6 @@
   ~UnsentLogStoreMetricsImpl() override {}
 
   // UnsentLogStoreMetrics:
-  void RecordLogReadStatus(
-    UnsentLogStoreMetrics::LogReadStatus status) override;
   void RecordCompressionRatio(
     size_t compressed_size, size_t original_size) override;
   void RecordDroppedLogSize(size_t size) override;
diff --git a/components/performance_manager/graph/process_node_impl.cc b/components/performance_manager/graph/process_node_impl.cc
index 1b8602e..7169e46a 100644
--- a/components/performance_manager/graph/process_node_impl.cc
+++ b/components/performance_manager/graph/process_node_impl.cc
@@ -14,9 +14,55 @@
 #include "components/performance_manager/graph/worker_node_impl.h"
 #include "components/performance_manager/public/execution_context/execution_context_registry.h"
 #include "components/performance_manager/v8_memory/v8_context_tracker.h"
+#include "content/public/browser/background_tracing_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 
 namespace performance_manager {
 
+namespace {
+
+void FireBackgroundTracingTriggerOnUI(
+    const std::string& trigger_name,
+    content::BackgroundTracingManager* manager) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // Don't fire a trigger unless we're in an active tracing scenario.
+  // Renderer-initiated background tracing triggers are always "preemptive"
+  // traces so we expect a scenario to be active.
+  if (!manager)
+    manager = content::BackgroundTracingManager::GetInstance();
+  if (!manager->HasActiveScenario())
+    return;
+
+  static content::BackgroundTracingManager::TriggerHandle trigger_handle = -1;
+  if (trigger_handle == -1) {
+    trigger_handle = manager->RegisterTriggerType(trigger_name.c_str());
+  } else {
+    // We only expect to be configured for a single renderer-initiated
+    // background tracing trigger at a time. So, if we've already had one
+    // registered, then simply check to see if it matches. If it doesn't match,
+    // then ignore this trigger event entirely. This prevents an abusive
+    // renderer from creating arbitrarily many trigger events. It does allow an
+    // abusive renderer to consume the single trigger slot, preventing valid
+    // renderers from firing triggers, but this is not a big deal. Ideally we'd
+    // be able to see if the active scenario is for |trigger_name|, but the
+    // tracing manager doesn't support that functionality right now.
+    const std::string& registered_trigger_name =
+        manager->GetTriggerNameFromHandle(trigger_handle);
+    if (registered_trigger_name != trigger_name)
+      return;
+  }
+
+  // Actually fire the trigger. We don't need to know when the trace is being
+  // finalized so pass an empty callback.
+  manager->TriggerNamedEvent(
+      trigger_handle,
+      content::BackgroundTracingManager::StartedFinalizingCallback());
+}
+
+}  // namespace
+
 ProcessNodeImpl::ProcessNodeImpl(content::ProcessType process_type,
                                  RenderProcessHostProxy render_process_proxy)
     : process_type_(process_type),
@@ -111,6 +157,14 @@
   }
 }
 
+void ProcessNodeImpl::FireBackgroundTracingTrigger(
+    const std::string& trigger_name) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  content::GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(&FireBackgroundTracingTriggerOnUI, trigger_name, nullptr));
+}
+
 void ProcessNodeImpl::SetProcessExitStatus(int32_t exit_status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // This may occur as the first event seen in the case where the process
@@ -190,6 +244,13 @@
   priority_.SetAndMaybeNotify(this, priority);
 }
 
+// static
+void ProcessNodeImpl::FireBackgroundTracingTriggerOnUIForTesting(
+    const std::string& trigger_name,
+    content::BackgroundTracingManager* manager) {
+  FireBackgroundTracingTriggerOnUI(trigger_name, manager);
+}
+
 base::WeakPtr<ProcessNodeImpl> ProcessNodeImpl::GetWeakPtrOnUIThread() {
   // TODO(siggi): Validate thread context.
   return weak_this_;
diff --git a/components/performance_manager/graph/process_node_impl.h b/components/performance_manager/graph/process_node_impl.h
index a779538..71fc725 100644
--- a/components/performance_manager/graph/process_node_impl.h
+++ b/components/performance_manager/graph/process_node_impl.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_PERFORMANCE_MANAGER_GRAPH_PROCESS_NODE_IMPL_H_
 
 #include <memory>
+#include <string>
 
 #include "base/containers/flat_set.h"
 #include "base/macros.h"
@@ -26,6 +27,10 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 
+namespace content {
+class BackgroundTracingManager;
+}  // namespace content
+
 namespace performance_manager {
 
 class FrameNodeImpl;
@@ -74,6 +79,7 @@
   void OnRemoteIframeDetached(
       const blink::LocalFrameToken& parent_frame_token,
       const blink::RemoteFrameToken& remote_frame_token) override;
+  void FireBackgroundTracingTrigger(const std::string& trigger_name) override;
 
   void SetProcessExitStatus(int32_t exit_status);
   void SetProcess(base::Process process, base::Time launch_time);
@@ -160,6 +166,9 @@
   void set_priority(base::TaskPriority priority);
 
   void OnAllFramesInProcessFrozenForTesting() { OnAllFramesInProcessFrozen(); }
+  static void FireBackgroundTracingTriggerOnUIForTesting(
+      const std::string& trigger_name,
+      content::BackgroundTracingManager* manager);
 
   base::WeakPtr<ProcessNodeImpl> GetWeakPtrOnUIThread();
   base::WeakPtr<ProcessNodeImpl> GetWeakPtr();
diff --git a/components/performance_manager/graph/process_node_impl_unittest.cc b/components/performance_manager/graph/process_node_impl_unittest.cc
index 092fbab..d069100 100644
--- a/components/performance_manager/graph/process_node_impl_unittest.cc
+++ b/components/performance_manager/graph/process_node_impl_unittest.cc
@@ -11,6 +11,8 @@
 #include "components/performance_manager/public/render_process_host_proxy.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
 #include "components/performance_manager/test_support/mock_graphs.h"
+#include "content/public/browser/background_tracing_config.h"
+#include "content/public/browser/background_tracing_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -144,6 +146,7 @@
 
 using testing::_;
 using testing::Invoke;
+using testing::Return;
 
 }  // namespace
 
@@ -264,4 +267,111 @@
             public_process_node->GetResidentSetKb());
 }
 
+namespace {
+
+class LenientFakeBackgroundTracingManager
+    : public content::BackgroundTracingManager {
+ public:
+  LenientFakeBackgroundTracingManager() = default;
+  ~LenientFakeBackgroundTracingManager() override = default;
+
+  // Functions we want to intercept.
+  MOCK_METHOD(TriggerHandle,
+              RegisterTriggerType,
+              (base::StringPiece trigger_name),
+              (override));
+  MOCK_METHOD(std::string,
+              GetTriggerNameFromHandleImpl,
+              (TriggerHandle trigger_handle),
+              ());
+  MOCK_METHOD(bool, HasActiveScenario, (), (override));
+  MOCK_METHOD(void,
+              TriggerNamedEvent,
+              (TriggerHandle trigger_handle,
+               StartedFinalizingCallback started_callback),
+              (override));
+
+  // GMOck can't handle return-by-reference, so we indirect through a wrapper.
+  const std::string& GetTriggerNameFromHandle(
+      TriggerHandle trigger_handle) override {
+    static std::string name;
+    name = GetTriggerNameFromHandleImpl(trigger_handle);
+    return name;
+  }
+
+  // Functions we don't care about.
+  virtual bool SetActiveScenario(
+      std::unique_ptr<content::BackgroundTracingConfig> config,
+      ReceiveCallback receive_callback,
+      DataFiltering data_filtering) override {
+    return true;
+  }
+  virtual void WhenIdle(IdleCallback idle_callback) override {}
+  virtual bool HasTraceToUpload() override { return false; }
+  virtual std::string GetLatestTraceToUpload() override {
+    return std::string();
+  }
+  virtual std::string GetBackgroundTracingUploadUrl(
+      const std::string& trial_name) override {
+    return std::string();
+  }
+  virtual std::unique_ptr<content::BackgroundTracingConfig>
+  GetBackgroundTracingConfig(const std::string& trial_name) override {
+    return std::unique_ptr<content::BackgroundTracingConfig>();
+  }
+  virtual void AbortScenarioForTesting() override {}
+  virtual void SetTraceToUploadForTesting(
+      std::unique_ptr<std::string> trace_data) override {}
+  virtual void SetConfigTextFilterForTesting(
+      ConfigTextFilterForTesting predicate) override {}
+};
+
+using FakeBackgroundTracingManager =
+    ::testing::StrictMock<LenientFakeBackgroundTracingManager>;
+
+}  // namespace
+
+TEST_F(ProcessNodeImplTest, FireBackgroundTracingTriggerOnUI) {
+  const std::string kTrigger1("trigger1");
+  const std::string kTrigger2("trigger2");
+  constexpr content::BackgroundTracingManager::TriggerHandle kHandle1 = 1;
+
+  FakeBackgroundTracingManager manager;
+
+  // Don't expect any other functions exception HasActiveScenario to be called
+  // it that function returns false.
+  EXPECT_CALL(manager, HasActiveScenario()).WillOnce(Return(false));
+  ProcessNodeImpl::FireBackgroundTracingTriggerOnUIForTesting(kTrigger1,
+                                                              &manager);
+  testing::Mock::VerifyAndClear(&manager);
+
+  // If HasActiveScenario returns true, expect a new trigger to be registered
+  // and triggered.
+  EXPECT_CALL(manager, HasActiveScenario()).WillOnce(Return(true));
+  EXPECT_CALL(manager, RegisterTriggerType(_)).WillOnce(Return(kHandle1));
+  EXPECT_CALL(manager, TriggerNamedEvent(_, _));
+  ProcessNodeImpl::FireBackgroundTracingTriggerOnUIForTesting(kTrigger1,
+                                                              &manager);
+  testing::Mock::VerifyAndClear(&manager);
+
+  // Now that a trigger is registered, expect the trigger to be validated, and
+  // triggered again.
+  EXPECT_CALL(manager, HasActiveScenario()).WillOnce(Return(true));
+  EXPECT_CALL(manager, GetTriggerNameFromHandleImpl(kHandle1))
+      .WillOnce(Return(kTrigger1));
+  EXPECT_CALL(manager, TriggerNamedEvent(_, _));
+  ProcessNodeImpl::FireBackgroundTracingTriggerOnUIForTesting(kTrigger1,
+                                                              &manager);
+  testing::Mock::VerifyAndClear(&manager);
+
+  // Now that a trigger is registered, expect a call with another trigger to
+  // be looked up, fail, and the trigger not to be invoked.
+  EXPECT_CALL(manager, HasActiveScenario()).WillOnce(Return(true));
+  EXPECT_CALL(manager, GetTriggerNameFromHandleImpl(kHandle1))
+      .WillOnce(Return(kTrigger1));
+  ProcessNodeImpl::FireBackgroundTracingTriggerOnUIForTesting(kTrigger2,
+                                                              &manager);
+  testing::Mock::VerifyAndClear(&manager);
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/public/mojom/coordination_unit.mojom b/components/performance_manager/public/mojom/coordination_unit.mojom
index a42b6654..6660e5c 100644
--- a/components/performance_manager/public/mojom/coordination_unit.mojom
+++ b/components/performance_manager/public/mojom/coordination_unit.mojom
@@ -112,4 +112,10 @@
   // by OnRemoteIframeAttached.
   OnRemoteIframeDetached(blink.mojom.LocalFrameToken parent_frame_token,
                          blink.mojom.RemoteFrameToken remote_frame_token);
+
+  // Called when a renderer has observed an allow-listed performance.mark
+  // trigger event. The browser will reconfirm that the trigger is indeed
+  // allow-listed before firing the background tracing trigger, which can result
+  // in a trace being uploaded to the background tracing pipeline.
+  FireBackgroundTracingTrigger(string trigger_name);
 };
diff --git a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
index 7ac9c0e..c67646a 100644
--- a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
+++ b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
@@ -14,7 +14,6 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import org.chromium.base.BuildConfig;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
@@ -55,9 +54,7 @@
      *         application context is not available.
      */
     private SharedPreferences getSharedPreferences() {
-        if (BuildConfig.DCHECK_IS_ON) {
-            assert mReadable;
-        }
+        assert mReadable;
         mThreadChecker.assertOnValidThread();
         if (mSharedPreferences == null) {
             Context context = ContextUtils.getApplicationContext();
diff --git a/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java b/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java
index b3e46acf..3985851b 100644
--- a/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java
+++ b/components/policy/android/junit/src/org/chromium/components/policy/PolicyCacheTest.java
@@ -209,7 +209,7 @@
                 Pair.create(POLICY_NAME_5, Pair.create(PolicyCache.Type.Dict, "{1:2}"))));
 
         Assert.assertFalse(mPolicyCache.isReadable());
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             assertAssertionError(() -> mPolicyCache.getIntValue(POLICY_NAME));
             assertAssertionError(() -> mPolicyCache.getBooleanValue(POLICY_NAME_2));
             assertAssertionError(() -> mPolicyCache.getStringValue(POLICY_NAME_3));
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 4b3c6fe..aef01528 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -25134,6 +25134,30 @@
       run audio with higher priority to address certain performance issues with audio capture.
       This policy will be removed in the future.''',
     },
+    {
+      'name': 'ForcedLanguages',
+      'owners': ['igorruvinov@chromium.org', 'pastarmovj@chromium.org'],
+      'type': 'list',
+      'schema': {
+        'type': 'array',
+        'items': { 'type': 'string' },
+      },
+      'supported_on': ['chrome.*:91-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': [ 'en-US' ],
+      'id': 839,
+      'default': None,
+      'caption': '''Configure the content and order of preferred languages''',
+      'tags': [],
+      'desc': '''This policy allows admins to configure the order of the preferred languages in <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s settings.
+
+      The order of the list will appear in the same order under the "Order languages based on your preference" section in chrome://settings/languages. Users won't be able to remove or reorder languages set by the policy, but will be able to add languages underneath those set by the policy. Users will also have full control over the browser's UI language and translation/spell check settings, unless enforced by other policies.
+
+      Leaving the policy unset lets users manipulate the entire list of preferred languages.''',
+    }
   ],
   'messages': {
     # Messages that are not associated to any policies.
@@ -26063,6 +26087,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 838,
+  'highest_id_currently_used': 839,
   'highest_atomic_group_id_currently_used': 40
 }
diff --git a/components/sessions/core/tab_restore_service_helper.cc b/components/sessions/core/tab_restore_service_helper.cc
index 538c7ce..6395bb9c 100644
--- a/components/sessions/core/tab_restore_service_helper.cc
+++ b/components/sessions/core/tab_restore_service_helper.cc
@@ -104,6 +104,14 @@
   if (closing_contexts_.find(context) != closing_contexts_.end())
     return base::nullopt;
 
+  // Save the Window as well as the Tab if this is the last tab of an appp
+  // browser to ensure the tab will reopen in the correct app window.
+  if (context && context->GetTabCount() == 1 &&
+      !context->GetAppName().empty()) {
+    BrowserClosing(context);
+    return base::nullopt;
+  }
+
   auto local_tab = std::make_unique<Tab>();
   PopulateTab(local_tab.get(), index, context, live_tab);
   if (local_tab->navigations.empty())
diff --git a/components/sync/engine/commit_contribution_impl.h b/components/sync/engine/commit_contribution_impl.h
index d37b8ab..4d092ac6 100644
--- a/components/sync/engine/commit_contribution_impl.h
+++ b/components/sync/engine/commit_contribution_impl.h
@@ -77,8 +77,7 @@
   // all (i.e. there is no internet connection).
   base::OnceCallback<void(SyncCommitError)> on_full_commit_failure_callback_;
 
-  // Null if |type_| is not encrypted. Otherwise this is used to encrypt the
-  // committed entities.
+  // A non-owned pointer to cryptographer to encrypt entities.
   Cryptographer* const cryptographer_;
 
   const PassphraseType passphrase_type_;
diff --git a/components/sync/engine/model_type_registry.cc b/components/sync/engine/model_type_registry.cc
index 2e53429..f85be165 100644
--- a/components/sync/engine/model_type_registry.cc
+++ b/components/sync/engine/model_type_registry.cc
@@ -51,19 +51,14 @@
 
 }  // namespace
 
-ModelTypeRegistry::ModelTypeRegistry(
-    NudgeHandler* nudge_handler,
-    CancelationSignal* cancelation_signal,
-    SyncEncryptionHandler* sync_encryption_handler)
+ModelTypeRegistry::ModelTypeRegistry(NudgeHandler* nudge_handler,
+                                     CancelationSignal* cancelation_signal,
+                                     KeystoreKeysHandler* keystore_keys_handler)
     : nudge_handler_(nudge_handler),
       cancelation_signal_(cancelation_signal),
-      sync_encryption_handler_(sync_encryption_handler) {
-  sync_encryption_handler_->AddObserver(this);
-}
+      keystore_keys_handler_(keystore_keys_handler) {}
 
-ModelTypeRegistry::~ModelTypeRegistry() {
-  sync_encryption_handler_->RemoveObserver(this);
-}
+ModelTypeRegistry::~ModelTypeRegistry() = default;
 
 void ModelTypeRegistry::ConnectDataType(
     ModelType type,
@@ -80,16 +75,22 @@
   bool initial_sync_done =
       activation_response->model_type_state.initial_sync_done();
 
+  DCHECK(!encrypted_types_.Has(type) || cryptographer_)
+      << "Connecting encrypted type " << ModelTypeToString(type)
+      << " but the cryptographer isn't set";
+
   auto worker = std::make_unique<ModelTypeWorker>(
       type, activation_response->model_type_state,
       /*trigger_initial_sync=*/!initial_sync_done,
-      encrypted_types_.Has(type) ? sync_encryption_handler_->GetCryptographer()
-                                 : nullptr,
+      encrypted_types_.Has(type) ? cryptographer_->Clone() : nullptr,
       passphrase_type_, nudge_handler_,
       std::move(activation_response->type_processor), cancelation_signal_);
 
-  worker->SetFallbackCryptographerForUma(
-      sync_encryption_handler_->GetCryptographer());
+  // If the cryptographer wasn't set yet, it will be informed to this |worker|
+  // as soon as it's set in OnCryptographerStateChanged().
+  if (cryptographer_) {
+    worker->UpdateFallbackCryptographerForUma(cryptographer_->Clone());
+  }
 
   // Save a raw pointer and add the worker to our structures.
   ModelTypeWorker* worker_ptr = worker.get();
@@ -170,7 +171,7 @@
 }
 
 KeystoreKeysHandler* ModelTypeRegistry::keystore_keys_handler() {
-  return sync_encryption_handler_->GetKeystoreKeysHandler();
+  return keystore_keys_handler_;
 }
 
 bool ModelTypeRegistry::HasUnsyncedItems() const {
@@ -216,24 +217,22 @@
 
 void ModelTypeRegistry::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                                 bool encrypt_everything) {
-  DCHECK(encrypted_types.HasAll(encrypted_types_))
-      << "ModelTypeRegistry doesn't support removing encrypted types.";
-  for (const auto& worker : connected_model_type_workers_) {
-    if (encrypted_types.Has(worker->GetModelType()) &&
-        !encrypted_types_.Has(worker->GetModelType())) {
-      worker->EnableEncryption(sync_encryption_handler_->GetCryptographer());
-    }
-  }
+  // TODO(skym): This does not handle reducing the number of encrypted types
+  // correctly. They're removed from |encrypted_types_| but corresponding
+  // workers never have their Cryptographers removed. This probably is not a use
+  // case that currently needs to be supported, but it should be guarded against
+  // here.
   encrypted_types_ = encrypted_types;
+  UpdateCryptographerForConnectedEncryptedTypes();
 }
 
 void ModelTypeRegistry::OnCryptographerStateChanged(
     Cryptographer* cryptographer,
     bool has_pending_keys) {
+  cryptographer_ = cryptographer->Clone();
+  UpdateCryptographerForConnectedEncryptedTypes();
   for (const auto& worker : connected_model_type_workers_) {
-    if (encrypted_types_.Has(worker->GetModelType())) {
-      worker->OnCryptographerChange();
-    }
+    worker->UpdateFallbackCryptographerForUma(cryptographer_->Clone());
   }
 }
 
@@ -247,4 +246,15 @@
   }
 }
 
+void ModelTypeRegistry::UpdateCryptographerForConnectedEncryptedTypes() {
+  for (const auto& worker : connected_model_type_workers_) {
+    if (encrypted_types_.Has(worker->GetModelType())) {
+      DCHECK(cryptographer_)
+          << ModelTypeToString(worker->GetModelType())
+          << " is a connected encrypted type but there's no cryptographer";
+      worker->UpdateCryptographer(cryptographer_->Clone());
+    }
+  }
+}
+
 }  // namespace syncer
diff --git a/components/sync/engine/model_type_registry.h b/components/sync/engine/model_type_registry.h
index a3da783..fef8ad0 100644
--- a/components/sync/engine/model_type_registry.h
+++ b/components/sync/engine/model_type_registry.h
@@ -23,7 +23,7 @@
 
 class CancelationSignal;
 class CommitContributor;
-class SyncEncryptionHandler;
+class KeystoreKeysHandler;
 class ModelTypeWorker;
 class UpdateHandler;
 
@@ -34,11 +34,9 @@
 class ModelTypeRegistry : public ModelTypeConnector,
                           public SyncEncryptionHandler::Observer {
  public:
-  // |nudge_handler|, |cancelation_signal| and |sync_encryption_handler| must
-  // outlive this object.
   ModelTypeRegistry(NudgeHandler* nudge_handler,
                     CancelationSignal* cancelation_signal,
-                    SyncEncryptionHandler* sync_encryption_handler);
+                    KeystoreKeysHandler* keystore_keys_handler);
   ~ModelTypeRegistry() override;
 
   // Implementation of ModelTypeConnector.
@@ -88,6 +86,9 @@
   base::WeakPtr<ModelTypeConnector> AsWeakPtr();
 
  private:
+  // Called when either |cryptographer_| or |encrypted_types_| change.
+  void UpdateCryptographerForConnectedEncryptedTypes();
+
   // Enabled proxy types, which don't have a worker.
   ModelTypeSet enabled_proxy_types_;
 
@@ -98,6 +99,9 @@
   UpdateHandlerMap update_handler_map_;
   CommitContributorMap commit_contributor_map_;
 
+  // A copy of the most recent cryptographer.
+  std::unique_ptr<Cryptographer> cryptographer_;
+
   // A copy of the most recent passphrase type.
   PassphraseType passphrase_type_ =
       SyncEncryptionHandler::kInitialPassphraseType;
@@ -111,7 +115,7 @@
   // ModelTypeWorker to cancel blocking operation.
   CancelationSignal* const cancelation_signal_;
 
-  SyncEncryptionHandler* const sync_encryption_handler_;
+  KeystoreKeysHandler* const keystore_keys_handler_;
 
   base::WeakPtrFactory<ModelTypeRegistry> weak_ptr_factory_{this};
 
diff --git a/components/sync/engine/model_type_worker.cc b/components/sync/engine/model_type_worker.cc
index ec1e5c6..95bda2bf 100644
--- a/components/sync/engine/model_type_worker.cc
+++ b/components/sync/engine/model_type_worker.cc
@@ -153,7 +153,7 @@
     ModelType type,
     const sync_pb::ModelTypeState& initial_state,
     bool trigger_initial_sync,
-    Cryptographer* cryptographer,
+    std::unique_ptr<Cryptographer> cryptographer,
     PassphraseType passphrase_type,
     NudgeHandler* nudge_handler,
     std::unique_ptr<ModelTypeProcessor> model_type_processor,
@@ -161,7 +161,7 @@
     : type_(type),
       model_type_state_(initial_state),
       model_type_processor_(std::move(model_type_processor)),
-      cryptographer_(cryptographer),
+      cryptographer_(std::move(cryptographer)),
       passphrase_type_(passphrase_type),
       nudge_handler_(nudge_handler),
       min_gu_responses_to_ignore_key_(kMinGuResponsesToIgnoreKey),
@@ -183,15 +183,15 @@
   // type state that has already done its initial sync, and is going to be
   // tracking metadata changes, however it does not have the most recent
   // encryption key name. The cryptographer was updated while the worker was not
-  // around, and we're not going to receive the usual OnCryptographerChange() or
+  // around, and we're not going to receive the normal UpdateCryptographer() or
   // EncryptionAcceptedApplyUpdates() calls to drive this process.
   //
   // If |cryptographer_->CanEncrypt()| is false, all the rest of this logic can
-  // be safely skipped, since |OnCryptographerChange()| must be called first
+  // be safely skipped, since |UpdateCryptographer(...)| must be called first
   // and things should be driven normally after that.
   //
   // If |model_type_state_.initial_sync_done()| is false, |model_type_state_|
-  // may still need to be updated, since OnCryptographerChange() will never
+  // may still need to be updated, since UpdateCryptographer() is never going to
   // happen, but we can assume PassiveApplyUpdates(...) will push the state to
   // the processor, and we should not push it now. In fact, doing so now would
   // violate the processor's assumption that the first OnUpdateReceived is will
@@ -215,29 +215,22 @@
   return type_;
 }
 
-void ModelTypeWorker::EnableEncryption(Cryptographer* cryptographer) {
+void ModelTypeWorker::UpdateCryptographer(
+    std::unique_ptr<Cryptographer> cryptographer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!cryptographer_);
   DCHECK(cryptographer);
-  cryptographer_ = cryptographer;
-  OnCryptographerChange();
-}
-
-void ModelTypeWorker::SetFallbackCryptographerForUma(
-    Cryptographer* fallback_cryptographer_for_uma) {
-  DCHECK(!fallback_cryptographer_for_uma_);
-  DCHECK(fallback_cryptographer_for_uma);
-  fallback_cryptographer_for_uma_ = fallback_cryptographer_for_uma;
-}
-
-void ModelTypeWorker::OnCryptographerChange() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cryptographer_);
+  cryptographer_ = std::move(cryptographer);
   UpdateEncryptionKeyName();
   DecryptStoredEntities();
   NudgeIfReadyToCommit();
 }
 
+void ModelTypeWorker::UpdateFallbackCryptographerForUma(
+    std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma) {
+  DCHECK(fallback_cryptographer_for_uma);
+  fallback_cryptographer_for_uma_ = std::move(fallback_cryptographer_for_uma);
+}
+
 void ModelTypeWorker::UpdatePassphraseType(PassphraseType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   passphrase_type_ = type;
@@ -288,8 +281,8 @@
     }
 
     UpdateResponseData response_data;
-    switch (PopulateUpdateResponseData(cryptographer_, type_, *update_entity,
-                                       &response_data)) {
+    switch (PopulateUpdateResponseData(cryptographer_.get(), type_,
+                                       *update_entity, &response_data)) {
       case SUCCESS:
         pending_updates_.push_back(std::move(response_data));
         // Override any previously undecryptable update for the same id.
@@ -543,7 +536,8 @@
                      weak_ptr_factory_.GetWeakPtr()),
       base::BindOnce(&ModelTypeWorker::OnFullCommitFailure,
                      weak_ptr_factory_.GetWeakPtr()),
-      cryptographer_, passphrase_type_, CommitOnlyTypes().Has(GetModelType()));
+      cryptographer_.get(), passphrase_type_,
+      CommitOnlyTypes().Has(GetModelType()));
 }
 
 bool ModelTypeWorker::HasLocalChangesForTest() const {
@@ -619,8 +613,8 @@
     const sync_pb::SyncEntity& encrypted_update = it->second;
 
     UpdateResponseData response_data;
-    switch (PopulateUpdateResponseData(cryptographer_, type_, encrypted_update,
-                                       &response_data)) {
+    switch (PopulateUpdateResponseData(cryptographer_.get(), type_,
+                                       encrypted_update, &response_data)) {
       case SUCCESS:
         pending_updates_.push_back(std::move(response_data));
         it = entries_pending_decryption_.erase(it);
@@ -808,7 +802,7 @@
   // |fallback_cryptographer_for_uma_| can decrypt the data.
   for (const auto& id_and_pending_update : entries_pending_decryption_) {
     UpdateResponseData ignored;
-    if (PopulateUpdateResponseData(fallback_cryptographer_for_uma_, type_,
+    if (PopulateUpdateResponseData(fallback_cryptographer_for_uma_.get(), type_,
                                    id_and_pending_update.second,
                                    &ignored) == SUCCESS) {
       base::UmaHistogramEnumeration(
diff --git a/components/sync/engine/model_type_worker.h b/components/sync/engine/model_type_worker.h
index e14a3bdc..1e128092 100644
--- a/components/sync/engine/model_type_worker.h
+++ b/components/sync/engine/model_type_worker.h
@@ -60,13 +60,10 @@
   // Public for testing.
   enum DecryptionStatus { SUCCESS, DECRYPTION_PENDING, FAILED_TO_DECRYPT };
 
-  // |nudge_handler| and |cancelation_signal| must outlive this object.
-  // |cryptographer| must either outlive this object or be null. Passing a
-  // a null cryptographer means this type won't use encryption.
   ModelTypeWorker(ModelType type,
                   const sync_pb::ModelTypeState& initial_state,
                   bool trigger_initial_sync,
-                  Cryptographer* cryptographer,
+                  std::unique_ptr<Cryptographer> cryptographer,
                   PassphraseType passphrase_type,
                   NudgeHandler* nudge_handler,
                   std::unique_ptr<ModelTypeProcessor> model_type_processor,
@@ -84,22 +81,12 @@
 
   ModelType GetModelType() const;
 
-  // Will start using |cryptographer| to encrypt/decrypt data. Must be called at
-  // most once, if the type transitioned from non-encrypted to encrypted.
-  // |cryptographer| must outlive this object.
-  void EnableEncryption(Cryptographer* cryptographer);
-
-  // Always called in production, may not be called in tests. Causes a worker
-  // without cryptographer to log whether |fallback_cryptographer_for_uma| would
-  // have been able to decrypt incoming encrypted updates.
-  // |fallback_cryptographer_for_uma| must outlive this object.
-  void SetFallbackCryptographerForUma(
-      Cryptographer* fallback_cryptographer_for_uma);
-
-  // Must only be called if there is a cryptographer. Called on every change to
-  // its state.
-  void OnCryptographerChange();
-
+  void UpdateCryptographer(std::unique_ptr<Cryptographer> cryptographer);
+  // Causes a worker without cryptographer to record whether
+  // |fallback_cryptographer_for_uma| would have been able to decrypt incoming
+  // encrypted updates. |fallback_cryptographer_for_uma| must be non-null.
+  void UpdateFallbackCryptographerForUma(
+      std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma);
   void UpdatePassphraseType(PassphraseType type);
 
   // UpdateHandler implementation.
@@ -222,23 +209,24 @@
   // Pointer to the ModelTypeProcessor associated with this worker. Never null.
   std::unique_ptr<ModelTypeProcessor> model_type_processor_;
 
-  // Initialized on construction or later via InitCryptographer(). Null as long
-  // as encryption is not enabled for this type.
-  Cryptographer* cryptographer_ = nullptr;
+  // A private copy of the most recent cryptographer known to sync.
+  // Initialized at construction time and updated with UpdateCryptographer().
+  // null if encryption is not enabled for this type.
+  std::unique_ptr<Cryptographer> cryptographer_;
 
   // Used to investigate an issue where the worker receives encrypted updates
   // despite not having a |cryptographer_| (|type_| is not an encrypted type).
   // In those cases, this will eventually hold the underlying cryptographer used
   // by other types. The worker then records whether that cryptographer is able
   // to decrypt the updates. See crbug.com/1178418.
-  Cryptographer* fallback_cryptographer_for_uma_ = nullptr;
+  std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma_;
 
   // A private copy of the most recent passphrase type. Initialized at
   // construction time and updated with UpdatePassphraseType().
   PassphraseType passphrase_type_;
 
   // Interface used to access and send nudges to the sync scheduler. Not owned.
-  NudgeHandler* const nudge_handler_;
+  NudgeHandler* nudge_handler_;
 
   // A map of sync entities, keyed by server_id. Holds updates encrypted with
   // pending keys. Entries are stored in a map for de-duplication (applying only
@@ -270,7 +258,7 @@
 
   // Cancellation signal is used to cancel blocking operation on engine
   // shutdown.
-  CancelationSignal* const cancelation_signal_;
+  CancelationSignal* cancelation_signal_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/components/sync/engine/model_type_worker_unittest.cc b/components/sync/engine/model_type_worker_unittest.cc
index ff71b18..8d427a0 100644
--- a/components/sync/engine/model_type_worker_unittest.cc
+++ b/components/sync/engine/model_type_worker_unittest.cc
@@ -115,12 +115,8 @@
   const ClientTagHash kHash2 = GenerateTagHash(kTag2);
   const ClientTagHash kHash3 = GenerateTagHash(kTag3);
 
-  ModelTypeWorkerTest()
-      : ModelTypeWorkerTest(PREFERENCES, /*is_encrypted_type=*/false) {}
-
-  ModelTypeWorkerTest(ModelType model_type, bool is_encrypted_type)
+  explicit ModelTypeWorkerTest(ModelType model_type = PREFERENCES)
       : model_type_(model_type),
-        is_encrypted_type_(is_encrypted_type),
         foreign_encryption_key_index_(0),
         update_encryption_filter_index_(0),
         mock_type_processor_(nullptr),
@@ -181,26 +177,14 @@
 
     worker_ = std::make_unique<ModelTypeWorker>(
         type, state, !state.initial_sync_done(),
-        is_encrypted_type_ ? &cryptographer_ : nullptr,
+        cryptographer_ ? cryptographer_->Clone() : nullptr,
         PassphraseType::kImplicitPassphrase, &mock_nudge_handler_,
         std::move(processor), &cancelation_signal_);
   }
 
-  // If the type isn't encrypted yet, makes the cryptographer available to the
-  // worker and marks the type as encrypted. Otherwise, just notifies a change
-  // in the cryptographer state.
-  void EnableEncryptionOrNotify() {
-    if (!worker()) {
-      // No worker to notify, just ensure |is_encrypted_type_| is true.
-      is_encrypted_type_ = true;
-      return;
-    }
-
-    if (is_encrypted_type_) {
-      worker()->OnCryptographerChange();
-    } else {
-      is_encrypted_type_ = true;
-      worker()->EnableEncryption(&cryptographer_);
+  void InitializeCryptographer() {
+    if (!cryptographer_) {
+      cryptographer_ = std::make_unique<FakeCryptographer>();
     }
   }
 
@@ -208,23 +192,32 @@
   // the cryptographer becomes unusable (no default key until the issue gets
   // resolved, via DecryptPendingKey()).
   void AddPendingKey() {
+    InitializeCryptographer();
+
     foreign_encryption_key_index_++;
-    cryptographer_.ClearDefaultEncryptionKey();
-    EnableEncryptionOrNotify();
+    cryptographer_->ClearDefaultEncryptionKey();
+
+    // Update the worker with the latest cryptographer.
+    if (worker()) {
+      worker()->UpdateCryptographer(cryptographer_->Clone());
+    }
   }
 
   // Update the local cryptographer with all relevant keys.
   void DecryptPendingKey() {
     DCHECK_NE(foreign_encryption_key_index_, 0);
+    InitializeCryptographer();
+
     std::string last_key_name;
     for (int i = 1; i <= foreign_encryption_key_index_; ++i) {
       last_key_name = GetNthKeyName(i);
-      cryptographer_.AddEncryptionKey(last_key_name);
+      cryptographer_->AddEncryptionKey(last_key_name);
     }
-    cryptographer_.SelectDefaultEncryptionKey(last_key_name);
+    cryptographer_->SelectDefaultEncryptionKey(last_key_name);
 
-    EnableEncryptionOrNotify();
+    // Update the worker with the latest cryptographer.
     if (worker()) {
+      worker()->UpdateCryptographer(cryptographer_->Clone());
       worker()->EncryptionAcceptedMaybeApplyUpdates();
     }
   }
@@ -409,23 +402,27 @@
 
   void ResetWorker() { worker_.reset(); }
 
+  // Returns the name of the encryption key in the cryptographer last passed to
+  // the CommitQueue. Returns an empty string if no cryptographer is
+  // in use. See also: DecryptPendingKey().
+  std::string GetLocalCryptographerKeyName() const {
+    if (!cryptographer_) {
+      return std::string();
+    }
+    return cryptographer_->GetDefaultEncryptionKeyName();
+  }
+
   MockModelTypeProcessor* processor() { return mock_type_processor_; }
   ModelTypeWorker* worker() { return worker_.get(); }
   SingleTypeMockServer* server() { return mock_server_.get(); }
   MockNudgeHandler* nudge_handler() { return &mock_nudge_handler_; }
   StatusController* status_controller() { return &status_controller_; }
-  std::string default_encryption_key_name() {
-    return cryptographer_.GetDefaultEncryptionKeyName();
-  }
 
  private:
   const ModelType model_type_;
 
-  FakeCryptographer cryptographer_;
-
-  // Determines whether |worker_| has access to the cryptographer or not.
-  // Can be set to true via EnableEncryptionOrNotify().
-  bool is_encrypted_type_;
+  // The cryptographer itself. Null if we're not encrypting the type.
+  std::unique_ptr<FakeCryptographer> cryptographer_;
 
   // The number of the most recent foreign encryption key known to our
   // cryptographer. Note that not all of these will be decryptable.
@@ -972,7 +969,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   // Normal commit request stuff.
@@ -1004,7 +1001,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   // Normal commit request stuff.
@@ -1153,7 +1150,7 @@
   // possible, so that it will have the chance to re-encrypt local data if
   // necessary.
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1171,7 +1168,7 @@
   // Init the cryptographer, it'll push the EKN.
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1190,7 +1187,7 @@
   // Now perform first sync and make sure the EKN makes it.
   TriggerTypeRootUpdateFromServer();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1208,7 +1205,7 @@
   // Now perform first sync and make sure the EKN makes it.
   TriggerTypeRootUpdateFromServer();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1236,7 +1233,7 @@
   const UpdateResponseData& update = processor()->GetUpdateResponse(kHash1);
   EXPECT_EQ(kTag1, update.entity.specifics.preference().name());
   EXPECT_EQ(kValue1, update.entity.specifics.preference().value());
-  EXPECT_EQ(default_encryption_key_name(), update.encryption_key_name);
+  EXPECT_EQ(GetLocalCryptographerKeyName(), update.encryption_key_name);
 }
 
 TEST_F(ModelTypeWorkerTest, OverwriteUndecryptableUpdateWithDecryptableOne) {
@@ -1301,10 +1298,9 @@
 
   // This isn't an encrypted type, so this worker has no cryptographer. Under
   // the hood however, the overall client does have a cryptographer containing
-  // key 1. That one is injected with SetFallbackCryptographerForUma().
-  std::unique_ptr<Cryptographer> cryptographer_for_uma =
-      FakeCryptographer::FromSingleDefaultKey(GetNthKeyName(1));
-  worker()->SetFallbackCryptographerForUma(cryptographer_for_uma.get());
+  // key 1. That one is injected with UpdateFallbackCryptographerForUma().
+  worker()->UpdateFallbackCryptographerForUma(
+      FakeCryptographer::FromSingleDefaultKey(GetNthKeyName(1)));
 
   // Send an update encrypted with key 1 and another encrypted with an unknown
   // key 2.
@@ -1326,9 +1322,6 @@
   histogram_tester.ExpectUniqueSample(
       "Sync.ModelTypeBlockedDueToUndecryptableUpdate",
       ModelTypeHistogramValue(worker()->GetModelType()), 1);
-
-  // The worker must not outlive |cryptographer_for_uma|.
-  ResetWorker();
 }
 
 TEST_F(ModelTypeWorkerTest, TimeUntilEncryptionKeyFoundMetric) {
@@ -1881,8 +1874,9 @@
  protected:
   const std::string kPassword = "SomePassword";
 
-  ModelTypeWorkerPasswordsTest()
-      : ModelTypeWorkerTest(PASSWORDS, /*is_encrypted_type=*/true) {}
+  ModelTypeWorkerPasswordsTest() : ModelTypeWorkerTest(PASSWORDS) {
+    InitializeCryptographer();
+  }
 };
 
 // Similar to EncryptedCommit but tests PASSWORDS specifically, which use a
@@ -1896,7 +1890,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(default_encryption_key_name(),
+  EXPECT_EQ(GetLocalCryptographerKeyName(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   EntitySpecifics specifics;
@@ -2071,8 +2065,7 @@
 // to test some special encryption requirements for BOOKMARKS.
 class ModelTypeWorkerBookmarksTest : public ModelTypeWorkerTest {
  protected:
-  ModelTypeWorkerBookmarksTest()
-      : ModelTypeWorkerTest(BOOKMARKS, /*is_encrypted_type=*/false) {}
+  ModelTypeWorkerBookmarksTest() : ModelTypeWorkerTest(BOOKMARKS) {}
 };
 
 TEST_F(ModelTypeWorkerBookmarksTest, CanDecryptUpdateWithMissingBookmarkGUID) {
diff --git a/components/sync/engine/nigori/cryptographer.h b/components/sync/engine/nigori/cryptographer.h
index f014664..9b471cb 100644
--- a/components/sync/engine/nigori/cryptographer.h
+++ b/components/sync/engine/nigori/cryptographer.h
@@ -19,6 +19,8 @@
   Cryptographer();
   virtual ~Cryptographer();
 
+  virtual std::unique_ptr<Cryptographer> Clone() const = 0;
+
   // Returns whether this cryptographer is ready to encrypt data, using
   // EncryptString(). This usually means that a default encryption key is
   // available and there are no pending keys.
diff --git a/components/sync/engine/sync_encryption_handler.h b/components/sync/engine/sync_encryption_handler.h
index 017c0d5c..f214158 100644
--- a/components/sync/engine/sync_encryption_handler.h
+++ b/components/sync/engine/sync_encryption_handler.h
@@ -123,10 +123,6 @@
   // even delete this API altogether.
   virtual bool Init() = 0;
 
-  // TODO(crbug.com/1178418): Add similar getters for the rest of the state
-  // notified to the observers.
-  virtual Cryptographer* GetCryptographer() = 0;
-
   // Attempts to re-encrypt encrypted data types using the passphrase provided.
   // Notifies observers of the result of the operation via OnPassphraseAccepted
   // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
diff --git a/components/sync/engine/sync_manager_impl.cc b/components/sync/engine/sync_manager_impl.cc
index 1e4a843..a8288c0 100644
--- a/components/sync/engine/sync_manager_impl.cc
+++ b/components/sync/engine/sync_manager_impl.cc
@@ -197,7 +197,9 @@
   }
 
   model_type_registry_ = std::make_unique<ModelTypeRegistry>(
-      this, args->cancelation_signal, sync_encryption_handler_);
+      this, args->cancelation_signal,
+      sync_encryption_handler_->GetKeystoreKeysHandler());
+  sync_encryption_handler_->AddObserver(model_type_registry_.get());
 
   // Build a SyncCycleContext and store the worker in it.
   DVLOG(1) << "Sync is bringing up SyncCycleContext.";
@@ -338,6 +340,9 @@
   scheduler_.reset();
   cycle_context_.reset();
 
+  if (model_type_registry_)
+    sync_encryption_handler_->RemoveObserver(model_type_registry_.get());
+
   model_type_registry_.reset();
 
   if (sync_encryption_handler_) {
diff --git a/components/sync/nigori/cryptographer_impl.cc b/components/sync/nigori/cryptographer_impl.cc
index 05dca19..443adf68 100644
--- a/components/sync/nigori/cryptographer_impl.cc
+++ b/components/sync/nigori/cryptographer_impl.cc
@@ -33,12 +33,15 @@
 std::unique_ptr<CryptographerImpl> CryptographerImpl::FromProto(
     const sync_pb::CryptographerData& proto) {
   NigoriKeyBag key_bag = NigoriKeyBag::CreateFromProto(proto.key_bag());
-  // TODO(crbug.com/1109221): An invalid local state should be handled in the
-  // caller instead of CHECK-ing here, e.g. by resetting the local state.
-  CHECK(proto.default_key_name().empty() ||
-        key_bag.HasKey(proto.default_key_name()));
-  return base::WrapUnique(
-      new CryptographerImpl(std::move(key_bag), proto.default_key_name()));
+  std::string default_encryption_key_name = proto.default_key_name();
+  if (!default_encryption_key_name.empty() &&
+      !key_bag.HasKey(default_encryption_key_name)) {
+    // A default key name was specified but not present among keys.
+    return nullptr;
+  }
+
+  return base::WrapUnique(new CryptographerImpl(
+      std::move(key_bag), std::move(default_encryption_key_name)));
 }
 
 CryptographerImpl::CryptographerImpl(NigoriKeyBag key_bag,
@@ -76,21 +79,10 @@
   default_encryption_key_name_ = key_name;
 }
 
-void CryptographerImpl::EmplaceKeysAndSelectDefaultKeyFrom(
-    const CryptographerImpl& other) {
-  EmplaceKeysFrom(other.key_bag_);
-  SelectDefaultEncryptionKey(other.default_encryption_key_name_);
-}
-
 void CryptographerImpl::ClearDefaultEncryptionKey() {
   default_encryption_key_name_.clear();
 }
 
-void CryptographerImpl::ClearAllKeys() {
-  default_encryption_key_name_.clear();
-  key_bag_ = NigoriKeyBag::CreateEmpty();
-}
-
 bool CryptographerImpl::HasKey(const std::string& key_name) const {
   return key_bag_.HasKey(key_name);
 }
@@ -100,7 +92,7 @@
   return key_bag_.ExportKey(default_encryption_key_name_);
 }
 
-std::unique_ptr<CryptographerImpl> CryptographerImpl::Clone() const {
+std::unique_ptr<CryptographerImpl> CryptographerImpl::CloneImpl() const {
   return base::WrapUnique(
       new CryptographerImpl(key_bag_.Clone(), default_encryption_key_name_));
 }
@@ -109,6 +101,10 @@
   return key_bag_.size();
 }
 
+std::unique_ptr<Cryptographer> CryptographerImpl::Clone() const {
+  return CloneImpl();
+}
+
 bool CryptographerImpl::CanEncrypt() const {
   return !default_encryption_key_name_.empty();
 }
diff --git a/components/sync/nigori/cryptographer_impl.h b/components/sync/nigori/cryptographer_impl.h
index f5d478f..d988518 100644
--- a/components/sync/nigori/cryptographer_impl.h
+++ b/components/sync/nigori/cryptographer_impl.h
@@ -61,22 +61,10 @@
   // return true. |key_name| must not be empty and must represent a known key.
   void SelectDefaultEncryptionKey(const std::string& key_name);
 
-  // Adds all keys in |other| that weren't previously known, and selects the
-  // same default key. |other| must have selected a default key.
-  void EmplaceKeysAndSelectDefaultKeyFrom(const CryptographerImpl& other);
-
   // Clears the default encryption key, which causes CanEncrypt() to return
   // false.
   void ClearDefaultEncryptionKey();
 
-  // Reverts the cryptographer to an empty one, i.e. what would be returned by
-  // CreateEmpty(). The default key is also cleared.
-  // The set of known encryption keys shouldn't decrease in general, since this
-  // may lead to data becoming undecryptable. This method can be called a) if
-  // sync is disabled, or b) if sync finds an error that can be solved by
-  // resetting the encryption state.
-  void ClearAllKeys();
-
   // Determines whether |key_name| represents a known key.
   bool HasKey(const std::string& key_name) const;
 
@@ -84,11 +72,13 @@
   // have a default encryption key set, as reflected by CanEncrypt().
   sync_pb::NigoriKey ExportDefaultKey() const;
 
-  std::unique_ptr<CryptographerImpl> Clone() const;
+  // Similar to Clone() but returns CryptographerImpl.
+  std::unique_ptr<CryptographerImpl> CloneImpl() const;
 
   size_t KeyBagSizeForTesting() const;
 
   // Cryptographer overrides.
+  std::unique_ptr<Cryptographer> Clone() const override;
   bool CanEncrypt() const override;
   bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const override;
   std::string GetDefaultEncryptionKeyName() const override;
diff --git a/components/sync/nigori/keystore_keys_cryptographer.cc b/components/sync/nigori/keystore_keys_cryptographer.cc
index b236a50..2b54650 100644
--- a/components/sync/nigori/keystore_keys_cryptographer.cc
+++ b/components/sync/nigori/keystore_keys_cryptographer.cc
@@ -72,13 +72,13 @@
 
 std::unique_ptr<KeystoreKeysCryptographer> KeystoreKeysCryptographer::Clone()
     const {
-  return base::WrapUnique(
-      new KeystoreKeysCryptographer(cryptographer_->Clone(), keystore_keys_));
+  return base::WrapUnique(new KeystoreKeysCryptographer(
+      cryptographer_->CloneImpl(), keystore_keys_));
 }
 
 std::unique_ptr<CryptographerImpl>
 KeystoreKeysCryptographer::ToCryptographerImpl() const {
-  return cryptographer_->Clone();
+  return cryptographer_->CloneImpl();
 }
 
 bool KeystoreKeysCryptographer::EncryptKeystoreDecryptorToken(
diff --git a/components/sync/nigori/nigori_key_bag.h b/components/sync/nigori/nigori_key_bag.h
index 809867d7..c11991d 100644
--- a/components/sync/nigori/nigori_key_bag.h
+++ b/components/sync/nigori/nigori_key_bag.h
@@ -30,8 +30,6 @@
   NigoriKeyBag(NigoriKeyBag&& other);
   ~NigoriKeyBag();
 
-  NigoriKeyBag& operator=(NigoriKeyBag&&) = default;
-
   void CopyFrom(const NigoriKeyBag& other);
 
   // Serialization to proto.
diff --git a/components/sync/nigori/nigori_state.cc b/components/sync/nigori/nigori_state.cc
index 37db313..2e6b79d 100644
--- a/components/sync/nigori/nigori_state.cc
+++ b/components/sync/nigori/nigori_state.cc
@@ -276,7 +276,7 @@
 
 NigoriState NigoriState::Clone() const {
   NigoriState result;
-  result.cryptographer = cryptographer->Clone();
+  result.cryptographer = cryptographer->CloneImpl();
   result.pending_keys = pending_keys;
   result.passphrase_type = passphrase_type;
   result.keystore_migration_time = keystore_migration_time;
diff --git a/components/sync/nigori/nigori_state.h b/components/sync/nigori/nigori_state.h
index 37a739d8..02ea4a3 100644
--- a/components/sync/nigori/nigori_state.h
+++ b/components/sync/nigori/nigori_state.h
@@ -51,8 +51,6 @@
 
   bool NeedsKeystoreReencryption() const;
 
-  // TODO(crbug.com/1109221): Make this const unique_ptr to avoid the object
-  // being destroyed after it's been injected to the ModelTypeWorker-s.
   std::unique_ptr<CryptographerImpl> cryptographer;
 
   // Pending keys represent a remote update that contained a keybag that cannot
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index bd0c5dc..204793c 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -659,11 +659,6 @@
   return this;
 }
 
-Cryptographer* NigoriSyncBridgeImpl::GetCryptographer() {
-  DCHECK(state_.cryptographer);
-  return state_.cryptographer.get();
-}
-
 bool NigoriSyncBridgeImpl::NeedKeystoreKey() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Explicitly asks the server for keystore keys if it's first-time sync, i.e.
@@ -1029,7 +1024,7 @@
   // storing explicit passphrase key in prefs, we need to find better solution.
   storage_->ClearData();
   state_.keystore_keys_cryptographer = KeystoreKeysCryptographer::CreateEmpty();
-  state_.cryptographer->ClearAllKeys();
+  state_.cryptographer = CryptographerImpl::CreateEmpty();
   state_.pending_keys.reset();
   state_.pending_keystore_decryptor_token.reset();
   state_.passphrase_type = NigoriSpecifics::UNKNOWN;
@@ -1045,7 +1040,7 @@
                                                   false);
 }
 
-const CryptographerImpl& NigoriSyncBridgeImpl::GetCryptographerImplForTesting()
+const CryptographerImpl& NigoriSyncBridgeImpl::GetCryptographerForTesting()
     const {
   return *state_.cryptographer;
 }
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index 342e34d8..92c34b0c 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -68,7 +68,6 @@
       const std::vector<std::vector<uint8_t>>& keys) override;
   base::Time GetKeystoreMigrationTime() const override;
   KeystoreKeysHandler* GetKeystoreKeysHandler() override;
-  Cryptographer* GetCryptographer() override;
 
   // KeystoreKeysHandler implementation.
   bool NeedKeystoreKey() const override;
@@ -82,8 +81,10 @@
   std::unique_ptr<EntityData> GetData() override;
   void ApplyDisableSyncChanges() override;
 
-  const CryptographerImpl& GetCryptographerImplForTesting() const;
-  // TODO(crbug.com/922900): Move these getters to SyncEncryptionHandler.
+  // TODO(crbug.com/922900): investigate whether we need this getter outside of
+  // tests and decide whether this method should be a part of
+  // SyncEncryptionHandler interface.
+  const CryptographerImpl& GetCryptographerForTesting() const;
   sync_pb::NigoriSpecifics::PassphraseType GetPassphraseTypeForTesting() const;
   ModelTypeSet GetEncryptedTypesForTesting() const;
   bool HasPendingKeysForTesting() const;
diff --git a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
index d994c96..6cc0cd5 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
@@ -361,7 +361,6 @@
   MockNigoriLocalChangeProcessor* processor() { return processor_; }
   MockObserver* observer() { return &observer_; }
   MockNigoriStorage* storage() { return storage_; }
-  Cryptographer* cryptographer() { return bridge_->GetCryptographer(); }
 
   const std::vector<uint8_t> kRawKeystoreKey = {0, 1, 2, 3, 4};
   const std::vector<uint8_t> kTrustedVaultKey = {2, 3, 4, 5, 6};
@@ -431,8 +430,9 @@
                                NotNull(), /*has_pending_keys=*/false));
   bridge()->SetDecryptionPassphrase(kKeyParams.password);
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams));
 }
 
 // Simplest case of keystore Nigori: we have only one keystore key and no old
@@ -463,8 +463,9 @@
   EXPECT_TRUE(bridge()->Init());
   EXPECT_THAT(bridge()->GetKeystoreMigrationTime(), Not(NullTime()));
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that client can properly process remote updates with rotated keystore
@@ -485,9 +486,10 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kOldKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kCurrentKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kCurrentKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kCurrentKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
 }
 
 // In the backward compatible mode keystore Nigori's keystore_decryptor_token
@@ -507,9 +509,10 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kGaiaKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kGaiaKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kGaiaKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kGaiaKeyParams));
 }
 
 TEST_F(NigoriSyncBridgeImplTest, ShouldExposeBackwardCompatibleKeystoreNigori) {
@@ -573,10 +576,11 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
   for (const KeyParams& key_params : kAllKeyParams) {
-    EXPECT_THAT(*cryptographer(), CanDecryptWith(key_params));
+    EXPECT_THAT(cryptographer, CanDecryptWith(key_params));
   }
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kCurrentKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
 }
 
 // Tests that we build keystore Nigori, put it to processor, initialize the
@@ -606,8 +610,9 @@
   EXPECT_EQ(bridge()->GetPassphraseTypeForTesting(),
             sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that upon receiving Nigori corrupted due to absence of
@@ -671,9 +676,10 @@
   EXPECT_THAT(bridge()->ApplySyncChanges(base::nullopt), Eq(base::nullopt));
   EXPECT_THAT(bridge()->GetData(), HasKeystoreNigori());
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams1));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams2));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams2));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams1));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams2));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams2));
 }
 
 // This test emulates late arrival of keystore keys, so neither
@@ -698,14 +704,15 @@
           EncryptedDataEq(entity_data.specifics.nigori().encryption_keybag())));
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
-  EXPECT_FALSE(cryptographer()->CanEncrypt());
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_FALSE(cryptographer.CanEncrypt());
 
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
   EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // This test emulates late arrival of keystore keys in backward-compatible
@@ -733,9 +740,10 @@
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
   bridge()->SetDecryptionPassphrase(kPassphraseKeyParams.password);
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
 
   // Regression part of the test, SetKeystoreKeys() call in this scenario used
   // to cause the crash (see crbug.com/1042203).
@@ -770,8 +778,9 @@
           EncryptedDataEq(expected_pending_keys)));
   bridge()->SetDecryptionPassphrase("wrong_passphrase");
 
-  EXPECT_THAT(bridge()->GetCryptographerImplForTesting().KeyBagSizeForTesting(),
-              Eq(size_t(0)));
+  const CryptographerImpl& cryptographer =
+      bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer.KeyBagSizeForTesting(), Eq(size_t(0)));
 }
 
 // Tests that attempt to SetEncryptionPassphrase() has no effect (at least
@@ -795,8 +804,9 @@
   // implemented. They might be not properly working with deferred state
   // change.
   EXPECT_THAT(bridge()->GetData(), HasKeystoreNigori());
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that we can perform initial sync with custom passphrase Nigori.
@@ -954,11 +964,11 @@
   ASSERT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
   ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
-  ASSERT_TRUE(cryptographer()->CanEncrypt());
+  ASSERT_TRUE(bridge()->GetCryptographerForTesting().CanEncrypt());
 
   EXPECT_CALL(*storage(), ClearData);
   bridge()->ApplyDisableSyncChanges();
-  EXPECT_FALSE(cryptographer()->CanEncrypt());
+  EXPECT_FALSE(bridge()->GetCryptographerForTesting().CanEncrypt());
 }
 
 // Tests decryption logic for explicit passphrase. In order to check that we're
@@ -991,10 +1001,10 @@
                                                    PASSPHRASE_BOOTSTRAP_TOKEN));
   bridge()->SetDecryptionPassphrase(passphrase_key_params.password);
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kOldKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(*cryptographer(),
-              HasDefaultKeyDerivedFrom(passphrase_key_params));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 INSTANTIATE_TEST_SUITE_P(Scrypt,
@@ -1046,9 +1056,9 @@
   const KeyParams passphrase_key_params = {
       bridge()->GetCustomPassphraseKeyDerivationParamsForTesting(),
       kCustomPassphrase};
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(*cryptographer(),
-              HasDefaultKeyDerivedFrom(passphrase_key_params));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 // Tests that pending local change with setting custom passphrase is applied,
@@ -1109,11 +1119,11 @@
   const KeyParams passphrase_key_params = {
       bridge()->GetCustomPassphraseKeyDerivationParamsForTesting(),
       kCustomPassphrase};
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams1));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams2));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(*cryptographer(),
-              HasDefaultKeyDerivedFrom(passphrase_key_params));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams1));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams2));
+  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 // Tests that SetEncryptionPassphrase() call doesn't lead to custom passphrase
@@ -1160,9 +1170,9 @@
   EXPECT_CALL(observer, OnPassphraseRequired).Times(0);
   ASSERT_THAT(bridge->MergeSyncData(std::move(entity_data)), Eq(base::nullopt));
 
-  EXPECT_THAT(*bridge->GetCryptographer(), CanDecryptWith(kKeyParams));
-  EXPECT_THAT(*bridge->GetCryptographer(),
-              HasDefaultKeyDerivedFrom(kKeyParams));
+  const Cryptographer& cryptographer = bridge->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams));
   bridge->RemoveObserver(&observer);
 }
 
@@ -1228,9 +1238,9 @@
       /*packed_keystore_keys=*/std::string());
 
   // Verify that we restored Cryptographer state.
-  EXPECT_THAT(*bridge2->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*bridge2->GetCryptographer(),
-              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Commit with keystore Nigori initialization might be not completed before
@@ -1275,9 +1285,9 @@
   EXPECT_EQ(bridge->GetPassphraseTypeForTesting(),
             sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
 
-  EXPECT_THAT(*bridge->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*bridge->GetCryptographer(),
-              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests the initial sync with a trusted vault Nigori. Observers should be
@@ -1498,9 +1508,10 @@
               Eq(AlwaysEncryptedUserTypes()));
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kTrustedVaultKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kTrustedVaultKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests processing of remote incremental update that transits from trusted
@@ -1551,9 +1562,10 @@
                                                    PASSPHRASE_BOOTSTRAP_TOKEN));
   bridge()->SetDecryptionPassphrase(kCustomPassphraseKeyParams.password);
 
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kTrustedVaultKeyParams));
-  EXPECT_THAT(*cryptographer(), CanDecryptWith(kCustomPassphraseKeyParams));
-  EXPECT_THAT(*cryptographer(),
+  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kTrustedVaultKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kCustomPassphraseKeyParams));
+  EXPECT_THAT(cryptographer,
               HasDefaultKeyDerivedFrom(kCustomPassphraseKeyParams));
 }
 
@@ -1732,7 +1744,7 @@
   ASSERT_FALSE(bridge()->HasPendingKeysForTesting());
 
   const CryptographerImpl& cryptographer =
-      bridge()->GetCryptographerImplForTesting();
+      bridge()->GetCryptographerForTesting();
   ASSERT_THAT(cryptographer,
               CanDecryptWith(TrustedVaultKeyParams(kTrustedVaultKey1)));
   EXPECT_THAT(cryptographer,
@@ -1793,12 +1805,11 @@
   EXPECT_THAT(bridge2->ApplySyncChanges(base::nullopt), Eq(base::nullopt));
   EXPECT_THAT(bridge2->GetData(), HasKeystoreNigori());
 
-  // Ensure the cryptographer corresponds to full keystore Nigori.
-  EXPECT_THAT(*bridge2->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(*bridge2->GetCryptographer(),
-              CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(*bridge2->GetCryptographer(),
-              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  // Ensure that |cryptographer| corresponds to full keystore Nigori.
+  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that upon startup bridge adds keystore keys into cryptographer, so it
@@ -1846,16 +1857,15 @@
       /*packed_explicit_passphrase_key=*/std::string(),
       /*packed_keystore_keys=*/std::string());
 
-  // Ensure the cryptographer can decrypt with keystore keys, but still has
-  // default key derived from custom passphrase.
+  // Ensure that |cryptographer| can decrypt with keystore keys, but still
+  // has default key derived from custom passphrase.
   const KeyParams kPassphraseKeyParams = {
       bridge2->GetCustomPassphraseKeyDerivationParamsForTesting(), kPassphrase};
-  EXPECT_THAT(*bridge2->GetCryptographer(),
+  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
+  EXPECT_THAT(cryptographer,
               CanDecryptWith(KeystoreKeyParams(kRawKeystoreKey)));
-  EXPECT_THAT(*bridge2->GetCryptographer(),
-              CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(*bridge2->GetCryptographer(),
-              HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
+  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
 }
 
 }  // namespace
diff --git a/components/sync/nigori/pending_local_nigori_commit.cc b/components/sync/nigori/pending_local_nigori_commit.cc
index c8b1bea..b9a0af3 100644
--- a/components/sync/nigori/pending_local_nigori_commit.cc
+++ b/components/sync/nigori/pending_local_nigori_commit.cc
@@ -121,12 +121,10 @@
       return false;
     }
 
-    std::unique_ptr<CryptographerImpl> cryptographer =
-        state->keystore_keys_cryptographer->ToCryptographerImpl();
-    DCHECK(!cryptographer->GetDefaultEncryptionKeyName().empty());
-    state->cryptographer->EmplaceKeysAndSelectDefaultKeyFrom(*cryptographer);
     state->passphrase_type = NigoriSpecifics::KEYSTORE_PASSPHRASE;
     state->keystore_migration_time = base::Time::Now();
+    state->cryptographer =
+        state->keystore_keys_cryptographer->ToCryptographerImpl();
     return true;
   }
 
diff --git a/components/sync/test/engine/fake_cryptographer.cc b/components/sync/test/engine/fake_cryptographer.cc
index e86a3b6..b729093 100644
--- a/components/sync/test/engine/fake_cryptographer.cc
+++ b/components/sync/test/engine/fake_cryptographer.cc
@@ -44,6 +44,13 @@
   default_key_name_.clear();
 }
 
+std::unique_ptr<Cryptographer> FakeCryptographer::Clone() const {
+  auto new_cryptographer = std::make_unique<FakeCryptographer>();
+  new_cryptographer->known_key_names_ = known_key_names_;
+  new_cryptographer->default_key_name_ = default_key_name_;
+  return new_cryptographer;
+}
+
 bool FakeCryptographer::CanEncrypt() const {
   return !default_key_name_.empty();
 }
diff --git a/components/sync/test/engine/fake_cryptographer.h b/components/sync/test/engine/fake_cryptographer.h
index ed14710..0754cdd 100644
--- a/components/sync/test/engine/fake_cryptographer.h
+++ b/components/sync/test/engine/fake_cryptographer.h
@@ -38,6 +38,7 @@
   void ClearDefaultEncryptionKey();
 
   // Cryptographer implementation.
+  std::unique_ptr<Cryptographer> Clone() const override;
   bool CanEncrypt() const override;
   bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const override;
   std::string GetDefaultEncryptionKeyName() const override;
diff --git a/components/sync/test/fake_sync_encryption_handler.cc b/components/sync/test/fake_sync_encryption_handler.cc
index 2385ed3..3a61407 100644
--- a/components/sync/test/fake_sync_encryption_handler.cc
+++ b/components/sync/test/fake_sync_encryption_handler.cc
@@ -71,9 +71,4 @@
   return this;
 }
 
-Cryptographer* FakeSyncEncryptionHandler::GetCryptographer() {
-  // GetCryptographer() must never return null.
-  return &fake_cryptographer_;
-}
-
 }  // namespace syncer
diff --git a/components/sync/test/fake_sync_encryption_handler.h b/components/sync/test/fake_sync_encryption_handler.h
index 7d9897b..9e2902a 100644
--- a/components/sync/test/fake_sync_encryption_handler.h
+++ b/components/sync/test/fake_sync_encryption_handler.h
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "components/sync/engine/nigori/keystore_keys_handler.h"
 #include "components/sync/engine/sync_encryption_handler.h"
-#include "components/sync/test/engine/fake_cryptographer.h"
 
 namespace syncer {
 
@@ -40,7 +39,6 @@
       const std::vector<std::vector<uint8_t>>& keys) override;
   base::Time GetKeystoreMigrationTime() const override;
   KeystoreKeysHandler* GetKeystoreKeysHandler() override;
-  Cryptographer* GetCryptographer() override;
 
   // KeystoreKeysHandler implementation.
   bool NeedKeystoreKey() const override;
@@ -49,7 +47,6 @@
  private:
   base::ObserverList<SyncEncryptionHandler::Observer>::Unchecked observers_;
   std::vector<uint8_t> keystore_key_;
-  FakeCryptographer fake_cryptographer_;
 };
 
 }  // namespace syncer
diff --git a/components/thin_webview/internal/compositor_view_impl.cc b/components/thin_webview/internal/compositor_view_impl.cc
index fa1c482..7b81229 100644
--- a/components/thin_webview/internal/compositor_view_impl.cc
+++ b/components/thin_webview/internal/compositor_view_impl.cc
@@ -76,6 +76,12 @@
 
 void CompositorViewImpl::SurfaceDestroyed(JNIEnv* env,
                                           const JavaParamRef<jobject>& object) {
+  // When we switch from Chrome to other app we can't detach child surface
+  // controls because it leads to a visible hole: b/157439199. To avoid this we
+  // don't detach surfaces if the surface is going to be destroyed, they will be
+  // detached and freed by OS.
+  compositor_->PreserveChildSurfaceControls();
+
   compositor_->SetSurface(nullptr, false);
   current_surface_format_ = kPixelFormatUnknown;
 }
diff --git a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java
index f54aefe..26beb1b 100644
--- a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java
+++ b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java
@@ -31,6 +31,7 @@
     private WindowAndroid mWindowAndroid;
     private long mNativeThinWebViewImpl;
     private WebContents mWebContents;
+    private WebContentsDelegateAndroid mWebContentsDelegate;
     private View mContentView;
 
     /**
@@ -67,10 +68,11 @@
             @Nullable WebContentsDelegateAndroid delegate) {
         if (mNativeThinWebViewImpl == 0) return;
         mWebContents = webContents;
-
+        // Native code holds only a weak reference to this object.
+        mWebContentsDelegate = delegate;
         setContentView(contentView);
         ThinWebViewImplJni.get().setWebContents(
-                mNativeThinWebViewImpl, ThinWebViewImpl.this, mWebContents, delegate);
+                mNativeThinWebViewImpl, ThinWebViewImpl.this, mWebContents, mWebContentsDelegate);
         mWebContents.onShow();
     }
 
diff --git a/components/translate/core/browser/translate_prefs.cc b/components/translate/core/browser/translate_prefs.cc
index ac4a2d46..fe5206ae 100644
--- a/components/translate/core/browser/translate_prefs.cc
+++ b/components/translate/core/browser/translate_prefs.cc
@@ -256,12 +256,15 @@
   language::ToChromeLanguageSynonym(&chrome_language);
 
   std::vector<std::string> languages;
+  std::vector<std::string> user_selected_languages;
   GetLanguageList(&languages);
+  GetUserSelectedLanguageList(&user_selected_languages);
 
   // We should block the language if the list does not already contain another
-  // language with the same base language.
+  // language with the same base language. Policy-forced languages aren't
+  // counted as "blocking", so only user-selected languages are checked.
   const bool should_block =
-      !ContainsSameBaseLanguage(languages, chrome_language);
+      !ContainsSameBaseLanguage(user_selected_languages, chrome_language);
 
   if (force_blocked || should_block) {
     BlockLanguage(input_language);
@@ -269,8 +272,8 @@
 
   // Add the language to the list.
   if (!base::Contains(languages, chrome_language)) {
-    languages.push_back(chrome_language);
-    language_prefs_->SetAcceptLanguagesList(languages);
+    user_selected_languages.push_back(chrome_language);
+    language_prefs_->SetUserSelectedLanguagesList(user_selected_languages);
   }
 }
 
@@ -281,24 +284,27 @@
   language::ToChromeLanguageSynonym(&chrome_language);
 
   std::vector<std::string> languages;
-  GetLanguageList(&languages);
+  std::vector<std::string> user_selected_languages;
+  GetUserSelectedLanguageList(&user_selected_languages);
 
   // Remove the language from the list.
-  const auto& it =
-      std::find(languages.begin(), languages.end(), chrome_language);
-  if (it != languages.end()) {
+  const auto& it = std::find(user_selected_languages.begin(),
+                             user_selected_languages.end(), chrome_language);
+  if (it != user_selected_languages.end()) {
     // If the language being removed is the most recent language, erase that
     // data so that Chrome won't try to translate to it next time Translate is
     // triggered.
     if (chrome_language == GetRecentTargetLanguage())
       ResetRecentTargetLanguage();
 
-    languages.erase(it);
-    PurgeUnsupportedLanguagesInLanguageFamily(chrome_language, &languages);
-    language_prefs_->SetAcceptLanguagesList(languages);
+    user_selected_languages.erase(it);
+    PurgeUnsupportedLanguagesInLanguageFamily(chrome_language,
+                                              &user_selected_languages);
+    language_prefs_->SetUserSelectedLanguagesList(user_selected_languages);
 
     // We should unblock the language if this was the last one from the same
     // language family.
+    GetLanguageList(&languages);
     if (!ContainsSameBaseLanguage(languages, chrome_language)) {
       UnblockLanguage(input_language);
     }
@@ -314,7 +320,7 @@
   DCHECK(!(offset < 1 && (where == kUp || where == kDown)));
 
   std::vector<std::string> languages;
-  GetLanguageList(&languages);
+  GetUserSelectedLanguageList(&languages);
 
   auto pos = std::find(languages.begin(), languages.end(), language);
   if (pos == languages.end())
@@ -339,11 +345,15 @@
       while (pos != languages.begin()) {
         auto next_pos = pos - 1;
         // Skip over non-enabled languages without decrementing |offset|.
-        if (std::binary_search(enabled.begin(), enabled.end(), *next_pos)) {
-          // By only checking |offset| when an enabled language is found, and
-          // decrementing |offset| after checking it (instead of before), this
-          // means that |language| will be moved up the list until it has either
-          // reached the next enabled language or the top of the list.
+        // Also skip over languages hidden due to duplication between forced
+        // and user-selected languages.
+        if (std::binary_search(enabled.begin(), enabled.end(), *next_pos) &&
+            !language_prefs_->IsForcedLanguage(*next_pos)) {
+          // By only checking |offset| when an enabled, non-forced language is
+          // found, and decrementing |offset| after checking it (instead of
+          // before), this means that |language| will be moved up the list until
+          // it has either reached the next enabled language or the top of the
+          // list.
           if (offset <= 0)
             break;
           --offset;
@@ -358,11 +368,13 @@
         return;
       for (auto next_pos = pos + 1; next_pos != languages.end() && offset > 0;
            pos = next_pos++) {
-        // Skip over non-enabled languages without decrementing offset. Unlike
-        // moving languages up in the list, moving languages down in the list
-        // stops as soon as |offset| reaches zero, instead of continuing to skip
-        // non-enabled languages after |offset| has reached zero.
-        if (std::binary_search(enabled.begin(), enabled.end(), *next_pos))
+        // Skip over non-enabled or forced languages without decrementing
+        // offset. Unlike moving languages up in the list, moving languages down
+        // in the list stops as soon as |offset| reaches zero, instead of
+        // continuing to skip non-enabled languages after |offset| has reached
+        // zero.
+        if (std::binary_search(enabled.begin(), enabled.end(), *next_pos) &&
+            !language_prefs_->IsForcedLanguage(*next_pos))
           --offset;
         std::swap(*next_pos, *pos);
       }
@@ -376,12 +388,12 @@
       return;
   }
 
-  language_prefs_->SetAcceptLanguagesList(languages);
+  language_prefs_->SetUserSelectedLanguagesList(languages);
 }
 
 void TranslatePrefs::SetLanguageOrder(
     const std::vector<std::string>& new_order) {
-  language_prefs_->SetAcceptLanguagesList(new_order);
+  language_prefs_->SetUserSelectedLanguagesList(new_order);
 }
 
 // static
@@ -784,6 +796,11 @@
   language_prefs_->GetAcceptLanguagesList(languages);
 }
 
+void TranslatePrefs::GetUserSelectedLanguageList(
+    std::vector<std::string>* const languages) const {
+  language_prefs_->GetUserSelectedLanguagesList(languages);
+}
+
 bool TranslatePrefs::CanTranslateLanguage(
     TranslateAcceptLanguages* accept_languages,
     base::StringPiece language) {
diff --git a/components/translate/core/browser/translate_prefs.h b/components/translate/core/browser/translate_prefs.h
index f54ab86..49514ed 100644
--- a/components/translate/core/browser/translate_prefs.h
+++ b/components/translate/core/browser/translate_prefs.h
@@ -308,9 +308,13 @@
   // Resets the prefs of denial state. Only used internally for diagnostics.
   void ResetDenialState();
 
-  // Gets the language list of the language settings.
+  // Gets the full (policy-forced and user selected) language list from language
+  // settings.
   void GetLanguageList(std::vector<std::string>* languages) const;
 
+  // Gets the user selected language list from language settings.
+  void GetUserSelectedLanguageList(std::vector<std::string>* languages) const;
+
   bool CanTranslateLanguage(TranslateAcceptLanguages* accept_languages,
                             base::StringPiece language);
   bool ShouldAutoTranslate(base::StringPiece original_language,
diff --git a/components/translate/core/browser/translate_prefs_unittest.cc b/components/translate/core/browser/translate_prefs_unittest.cc
index 32ca3ac5..00668b13 100644
--- a/components/translate/core/browser/translate_prefs_unittest.cc
+++ b/components/translate/core/browser/translate_prefs_unittest.cc
@@ -62,7 +62,7 @@
     TranslatePrefs::RegisterProfilePrefs(prefs_.registry());
     translate_prefs_ = std::make_unique<translate::TranslatePrefs>(&prefs_);
     accept_languages_tester_ =
-        std::make_unique<language::test::AcceptLanguagesTester>(&prefs_);
+        std::make_unique<language::test::LanguagePrefTester>(&prefs_);
     now_ = base::Time::Now();
     two_days_ago_ = now_ - base::TimeDelta::FromDays(2);
   }
@@ -133,8 +133,7 @@
 
   sync_preferences::TestingPrefServiceSyncable prefs_;
   std::unique_ptr<translate::TranslatePrefs> translate_prefs_;
-  std::unique_ptr<language::test::AcceptLanguagesTester>
-      accept_languages_tester_;
+  std::unique_ptr<language::test::LanguagePrefTester> accept_languages_tester_;
 
   // Shared time constants.
   base::Time now_;
@@ -474,7 +473,7 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->ResetBlockedLanguagesToDefault();
   translate_prefs_->AddToLanguageList("it-IT", /*force_blocked=*/false);
-  accept_languages_tester_->ExpectLanguagePrefs("en,it-IT");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it-IT");
   ExpectBlockedLanguageListContent({"en", "it"});
 
   // Force blocked false, language from same family already in list.
@@ -482,7 +481,7 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->ResetBlockedLanguagesToDefault();
   translate_prefs_->AddToLanguageList("es-ES", /*force_blocked=*/false);
-  accept_languages_tester_->ExpectLanguagePrefs("en,es-AR,es-ES");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,es-AR,es-ES");
   ExpectBlockedLanguageListContent({"en"});
 }
 
@@ -495,7 +494,7 @@
   translate_prefs_->BlockLanguage("en-US");
   translate_prefs_->BlockLanguage("es-AR");
   translate_prefs_->RemoveFromLanguageList("es-AR");
-  accept_languages_tester_->ExpectLanguagePrefs("en-US");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en-US");
   ExpectBlockedLanguageListContent({"en"});
 
   // Do not unblock if not the last language of a family.
@@ -505,7 +504,7 @@
   translate_prefs_->BlockLanguage("en-US");
   translate_prefs_->BlockLanguage("es-AR");
   translate_prefs_->RemoveFromLanguageList("es-AR");
-  accept_languages_tester_->ExpectLanguagePrefs("en-US,es-ES");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en-US,es-ES");
   ExpectBlockedLanguageListContent({"en", "es"});
 }
 
@@ -516,11 +515,11 @@
   std::vector<std::string> languages;
   languages = {"en", "en-US", "en-FOO"};
   accept_languages_tester_->SetLanguagePrefs(languages);
-  accept_languages_tester_->ExpectLanguagePrefs("en,en-US,en-FOO");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,en-US,en-FOO");
   translate_prefs_->RemoveFromLanguageList("en-US");
-  accept_languages_tester_->ExpectLanguagePrefs("en,en-FOO");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,en-FOO");
   translate_prefs_->RemoveFromLanguageList("en");
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 }
 
 TEST_F(TranslatePrefsTest, RemoveFromLanguageListClearsRecentLanguage) {
@@ -555,39 +554,39 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en-US", TranslatePrefs::kTop, offset,
                                       {"en-US"});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Search for empty string.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kTop, offset, {"en"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // List of enabled languages is empty.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kTop, offset, {});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Everything empty.
   languages = {""};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kTop, offset, {});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Only one element in the list.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kTop, offset,
                                       {"en-US"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Element is already at the top.
   languages = {"en", "fr"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kTop, offset,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,fr");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr");
 
   // Below we test cases that result in a valid rearrangement of the list.
 
@@ -597,35 +596,35 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kTop, offset,
                                       {"it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("it,en,fr,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("it,en,fr,es");
 
   // Swap two languages.
   languages = {"en", "fr"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kTop, offset,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,en");
 
   // Language in the middle.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kTop, offset,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("it,en,fr,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("it,en,fr,es");
 
   // Language at the bottom.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kTop, offset,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("es,en,fr,it");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("es,en,fr,it");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kTop, offset,
                                       {"en", "fr", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("zh,en,fr,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("zh,en,fr,it,es");
 }
 
 TEST_F(TranslatePrefsTest, MoveLanguageUp) {
@@ -641,45 +640,90 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en-US", TranslatePrefs::kUp, 1,
                                       {"en-US"});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Search for empty string.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kUp, 1, {"en"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // List of enabled languages is empty.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kUp, 1, {});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Everything empty.
   languages = {""};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kUp, 1, {});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Only one element in the list.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kUp, 1, {"en"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Element is already at the top.
   languages = {"en", "fr"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kUp, 1,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,fr");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr");
 
   // The language is at the top of the enabled languages.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kUp, 1,
                                       {"it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("it,en,fr,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("it,en,fr,es");
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+  //---------------------------------------------------------------------------
+  // Move with policy-forced languages present.
+  // Forced languages should always remain at the top of the languages list and
+  // can't be reordered.
+  // Only test on non-Chrome OS platforms.
+
+  // Try moving forced language up.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kUp, 1,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es,zh");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Try moving forced/user-selected duplicate languages.
+  languages = {"it", "es", "fr"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kUp, 1,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es");
+  accept_languages_tester_->ExpectSelectedLanguagePrefs("it,fr,es");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Move top selected language up by 1.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kUp, 1,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es,zh");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Try moving top selected language up to top of all languages.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kUp, 2,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es,zh");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+#endif
 
   //---------------------------------------------------------------------------
   // Below we test cases that result in a valid rearrangement of the list.
@@ -690,28 +734,28 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kUp, 1,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,en");
 
   // Language in the middle.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kUp, 1,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,fr,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,fr,es");
 
   // Language at the bottom.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kUp, 1,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,fr,es,it");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,es,it");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 1,
                                       {"en", "fr", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,zh,fr,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,zh,fr,it,es");
 
   //---------------------------------------------------------------------------
   // Move by more than 1 position.
@@ -721,49 +765,84 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kUp, 3,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("es,en,fr,it,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("es,en,fr,it,zh");
 
   // Move to the middle of the list.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kUp, 2,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,es,fr,it,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,es,fr,it,zh");
 
   // Move up the last language.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 3,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,zh,fr,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,zh,fr,it,es");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 2,
                                       {"en", "fr", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,zh,fr,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,zh,fr,it,es");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 2,
                                       {"en", "fr", "it", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,zh,fr,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,zh,fr,it,es");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh", "de", "pt"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("de", TranslatePrefs::kUp, 3,
                                       {"it", "es", "zh", "de", "pt"});
-  accept_languages_tester_->ExpectLanguagePrefs("de,en,fr,it,es,zh,pt");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("de,en,fr,it,es,zh,pt");
 
   // If offset is too large, we effectively move to the top.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kUp, 7,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("es,en,fr,it,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("es,en,fr,it,zh");
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+  //---------------------------------------------------------------------------
+  // Move with policy-forced languages present.
+  // Only test on non-Chrome OS platforms.
+
+  // Move bottom selected language to top of all languages.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 4,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,zh,it,es");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Move middle selected language to top of all languages.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("es", TranslatePrefs::kUp, 3,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,es,it,zh");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Moving selected language up should cause it to jump over hidden duplicate
+  // languages within the kSelectedLanguages pref.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "es", "fr"});
+  translate_prefs_->RearrangeLanguage("zh", TranslatePrefs::kUp, 1,
+                                      {"en", "es", "fr", "it", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,es,fr,zh,it");
+  accept_languages_tester_->ExpectSelectedLanguagePrefs("zh,it,es");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+#endif
 }
 
 TEST_F(TranslatePrefsTest, MoveLanguageDown) {
@@ -779,38 +858,38 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en-US", TranslatePrefs::kDown, 1,
                                       {"en-US"});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Search for empty string.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kDown, 1, {"en"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // List of enabled languages is empty.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1, {});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Everything empty.
   languages = {""};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("", TranslatePrefs::kDown, 1, {});
-  accept_languages_tester_->ExpectLanguagePrefs("");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("");
 
   // Only one element in the list.
   languages = {"en"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1, {"en"});
-  accept_languages_tester_->ExpectLanguagePrefs("en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en");
 
   // Element is already at the bottom.
   languages = {"en", "fr"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 1,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,fr");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr");
 
   // The language is at the bottom of the enabled languages: we move it to the
   // very bottom of the list.
@@ -818,7 +897,46 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kDown, 1,
                                       {"fr", "it"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,fr,es,it");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,es,it");
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+  //---------------------------------------------------------------------------
+  // Move with policy-forced languages present.
+  // Only test on non-Chrome OS platforms.
+
+  // Try moving forced language down.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 1,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es,zh");
+  translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1,
+                                      {"en", "fr", "it", "es", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es,zh");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Try moving forced/user-selected duplicate languages.
+  languages = {"en", "it", "es"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "fr"});
+  translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1,
+                                      {"en", "fr", "it", "es"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,fr,it,es");
+  accept_languages_tester_->ExpectSelectedLanguagePrefs("it,en,es");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+
+  // Moving selected language down should cause it to jump over hidden duplicate
+  // languages within the kSelectedLanguages pref.
+  languages = {"it", "es", "zh"};
+  accept_languages_tester_->SetLanguagePrefs(languages);
+  accept_languages_tester_->SetForcedLanguagePrefs({"en", "es", "fr"});
+  translate_prefs_->RearrangeLanguage("it", TranslatePrefs::kDown, 1,
+                                      {"en", "es", "fr", "it", "zh"});
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,es,fr,zh,it");
+  accept_languages_tester_->ExpectSelectedLanguagePrefs("es,zh,it");
+  accept_languages_tester_->SetForcedLanguagePrefs({});  // Reset pref
+#endif
 
   //---------------------------------------------------------------------------
   // Below we test cases that result in a valid rearrangement of the list.
@@ -829,28 +947,28 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1,
                                       {"en", "fr"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,en");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,en");
 
   // Language in the middle.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 1,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,fr,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,fr,es");
 
   // Language at the top.
   languages = {"en", "fr", "it", "es"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1,
                                       {"en", "fr", "it", "es"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,en,it,es");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,en,it,es");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 1,
                                       {"en", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,it,es,en,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,it,es,en,zh");
 
   //---------------------------------------------------------------------------
   // Move by more than 1 position.
@@ -860,49 +978,49 @@
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 3,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,es,zh,fr");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,es,zh,fr");
 
   // Move to the middle of the list.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 2,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,es,fr,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,es,fr,zh");
 
   // Move down the first language.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 3,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,it,es,en,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,it,es,en,zh");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 2,
                                       {"en", "fr", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,it,es,en,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,it,es,en,zh");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("en", TranslatePrefs::kDown, 2,
                                       {"en", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("fr,it,es,en,zh");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("fr,it,es,en,zh");
 
   // Skip languages that are not enabled.
   languages = {"en", "fr", "it", "es", "zh", "de", "pt"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 3,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,es,zh,fr,de,pt");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,es,zh,fr,de,pt");
 
   // If offset is too large, we effectively move to the bottom.
   languages = {"en", "fr", "it", "es", "zh"};
   accept_languages_tester_->SetLanguagePrefs(languages);
   translate_prefs_->RearrangeLanguage("fr", TranslatePrefs::kDown, 6,
                                       {"en", "fr", "it", "es", "zh"});
-  accept_languages_tester_->ExpectLanguagePrefs("en,it,es,zh,fr");
+  accept_languages_tester_->ExpectAcceptLanguagePrefs("en,it,es,zh,fr");
 }
 
 TEST_F(TranslatePrefsTest, SiteNeverPromptList) {
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
index 3f499dc..45aa25fb 100644
--- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
@@ -151,7 +151,7 @@
 
         @Override
         public String toString() {
-            if (BuildConfig.DCHECK_IS_ON) {
+            if (BuildConfig.ENABLE_ASSERTS) {
                 return "SeedInfo{signature=\"" + signature + "\" country=\"" + country
                         + "\" date=\"" + date + " isGzipCompressed=" + isGzipCompressed
                         + " seedData=" + Arrays.toString(seedData);
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 889a5f3..ff32445 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -1313,6 +1313,12 @@
   resource_provider_->SetAllowAccessToGPUThread(false);
 }
 
+void Display::PreserveChildSurfaceControls() {
+  if (skia_output_surface_) {
+    skia_output_surface_->PreserveChildSurfaceControls();
+  }
+}
+
 DelegatedInkPointRendererBase* Display::GetDelegatedInkPointRenderer() {
   return renderer_->GetDelegatedInkPointRenderer();
 }
diff --git a/components/viz/service/display/display.h b/components/viz/service/display/display.h
index fcea945..e401bf92 100644
--- a/components/viz/service/display/display.h
+++ b/components/viz/service/display/display.h
@@ -187,6 +187,7 @@
   void RemoveOverdrawQuads(AggregatedFrame* frame);
 
   void SetSupportedFrameIntervals(std::vector<base::TimeDelta> intervals);
+  void PreserveChildSurfaceControls();
 
   base::ScopedClosureRunner GetCacheBackBufferCb();
 
diff --git a/components/viz/service/display/skia_output_surface.h b/components/viz/service/display/skia_output_surface.h
index 0eee8b5..541c9d0 100644
--- a/components/viz/service/display/skia_output_surface.h
+++ b/components/viz/service/display/skia_output_surface.h
@@ -169,6 +169,11 @@
       base::OnceClosure callback,
       std::vector<gpu::SyncToken> sync_tokens) = 0;
 
+  // Android specific, asks GLSurfaceEGLSurfaceControl to not detach child
+  // surface controls during destruction. This is necessary for cases when we
+  // switch from chrome to other app, the OS will take care of the cleanup.
+  virtual void PreserveChildSurfaceControls() = 0;
+
   // Flush pending GPU tasks. This method returns a sync token which can be
   // waited on in a command buffer to ensure all pending tasks are executed on
   // the GPU main thread.
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 24a224f..f1641e6 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -1156,4 +1156,15 @@
   should_measure_next_post_task_ = true;
 }
 
+void SkiaOutputSurfaceImpl::PreserveChildSurfaceControls() {
+  // impl_on_gpu_ is released on the GPU thread by a posted task from
+  // SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
+  auto task =
+      base::BindOnce(&SkiaOutputSurfaceImplOnGpu::PreserveChildSurfaceControls,
+                     base::Unretained(impl_on_gpu_.get()));
+  EnqueueGpuTask(std::move(task), std::vector<gpu::SyncToken>(),
+                 /*make_current=*/false,
+                 /*need_framebuffer=*/false);
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 4a2ab3d..8fcbaa906 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -128,6 +128,7 @@
                   std::unique_ptr<CopyOutputRequest> request) override;
   void AddContextLostObserver(ContextLostObserver* observer) override;
   void RemoveContextLostObserver(ContextLostObserver* observer) override;
+  void PreserveChildSurfaceControls() override;
   gpu::SyncToken Flush() override;
 
 #if defined(OS_APPLE)
@@ -281,7 +282,10 @@
   // increments or flips.
   gfx::OverlayTransform display_transform_ = gfx::OVERLAY_TRANSFORM_NONE;
 
-  // |impl_on_gpu| is created and destroyed on the GPU thread.
+  // |impl_on_gpu| is created and destroyed on the GPU thread by a posted task
+  // from SkiaOutputSurfaceImpl::Initialize and SkiaOutputSurfaceImpl::dtor. So
+  // it's safe to use base::Unretained for posting tasks during life time of
+  // SkiaOutputSurfaceImpl.
   std::unique_ptr<SkiaOutputSurfaceImplOnGpu> impl_on_gpu_;
 
   sk_sp<GrContextThreadSafeProxy> gr_context_thread_safe_;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 4a530d5a..3b01854 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -1769,6 +1769,11 @@
   ScheduleCheckReadbackCompletion();
 }
 
+void SkiaOutputSurfaceImplOnGpu::PreserveChildSurfaceControls() {
+  if (gl_surface_)
+    gl_surface_->PreserveChildSurfaceControls();
+}
+
 #if defined(OS_APPLE)
 std::unique_ptr<gpu::SharedImageRepresentationSkia>
 SkiaOutputSurfaceImplOnGpu::GetOrCreateRenderPassOverlayBacking(
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index f86d769e..dcf594a1 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -212,6 +212,8 @@
 
   void ReleaseFenceSync(uint64_t sync_fence_release);
 
+  void PreserveChildSurfaceControls();
+
  private:
   class OffscreenSurface;
   class DisplayContext;
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index 378ff94..7aa82a0 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -308,6 +308,10 @@
   display_->SetSupportedFrameIntervals(supported_frame_intervals);
 }
 
+void RootCompositorFrameSinkImpl::PreserveChildSurfaceControls() {
+  display_->PreserveChildSurfaceControls();
+}
+
 #endif  // defined(OS_ANDROID)
 
 void RootCompositorFrameSinkImpl::AddVSyncParameterObserver(
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
index 4202781..b250496 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
@@ -74,6 +74,7 @@
   void UpdateRefreshRate(float refresh_rate) override;
   void SetSupportedRefreshRates(
       const std::vector<float>& supported_refresh_rates) override;
+  void PreserveChildSurfaceControls() override;
 #endif
   void AddVSyncParameterObserver(
       mojo::PendingRemote<mojom::VSyncParameterObserver> observer) override;
diff --git a/components/viz/test/fake_skia_output_surface.h b/components/viz/test/fake_skia_output_surface.h
index 5489ebd9..2cba17f 100644
--- a/components/viz/test/fake_skia_output_surface.h
+++ b/components/viz/test/fake_skia_output_surface.h
@@ -112,6 +112,8 @@
   sk_sp<SkDeferredDisplayList> EndPaintRenderPassOverlay() override;
 #endif
 
+  void PreserveChildSurfaceControls() override {}
+
   // ExternalUseClient implementation:
   gpu::SyncToken ReleaseImageContexts(
       const std::vector<std::unique_ptr<ImageContext>> image_contexts) override;
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 1dc1fea..20fc506 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -920,6 +920,11 @@
   cached_back_buffer_.reset();
 }
 
+void CompositorImpl::PreserveChildSurfaceControls() {
+  if (display_private_)
+    display_private_->PreserveChildSurfaceControls();
+}
+
 void CompositorImpl::RequestPresentationTimeForNextFrame(
     PresentationTimeCallback callback) {
   host_->RequestPresentationTimeForNextFrame(std::move(callback));
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 2b5d3c9..1f92027 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -105,6 +105,7 @@
   ui::ResourceManager& GetResourceManager() override;
   void CacheBackBufferForCurrentSurface() override;
   void EvictCachedBackBuffer() override;
+  void PreserveChildSurfaceControls() override;
   void RequestPresentationTimeForNextFrame(
       PresentationTimeCallback callback) override;
 
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index 845fe61..dd95a28 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -47,7 +47,7 @@
 
 class FrameTreeBrowserTest : public ContentBrowserTest {
  public:
-  FrameTreeBrowserTest() {}
+  FrameTreeBrowserTest() = default;
 
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
@@ -804,7 +804,7 @@
 
 class CrossProcessFrameTreeBrowserTest : public ContentBrowserTest {
  public:
-  CrossProcessFrameTreeBrowserTest() {}
+  CrossProcessFrameTreeBrowserTest() = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     IsolateAllSitesForTesting(command_line);
@@ -1243,7 +1243,7 @@
 // it from outsiders.
 class IsolateIcelandFrameTreeBrowserTest : public ContentBrowserTest {
  public:
-  IsolateIcelandFrameTreeBrowserTest() {}
+  IsolateIcelandFrameTreeBrowserTest() = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // Blink suppresses navigations to blob URLs of origins different from the
diff --git a/content/browser/renderer_host/frame_tree_node.h b/content/browser/renderer_host/frame_tree_node.h
index fcb8b12..c41f94c 100644
--- a/content/browser/renderer_host/frame_tree_node.h
+++ b/content/browser/renderer_host/frame_tree_node.h
@@ -60,7 +60,7 @@
     // Invoked when a FrameTreeNode becomes focused.
     virtual void OnFrameTreeNodeFocused(FrameTreeNode* node) {}
 
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
   };
 
   static const int kFrameTreeNodeInvalidId;
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 2160142..c93f0a78 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -2086,11 +2086,10 @@
   needs_reload_type_ = NeedsReloadType::kCopyStateFrom;
   InsertEntriesFrom(source, source->GetEntryCount());
 
-  for (auto it = source->session_storage_namespace_map_.begin();
-       it != source->session_storage_namespace_map_.end(); ++it) {
+  for (auto& it : source->session_storage_namespace_map_) {
     SessionStorageNamespaceImpl* source_namespace =
-        static_cast<SessionStorageNamespaceImpl*>(it->second.get());
-    session_storage_namespace_map_[it->first] = source_namespace->Clone();
+        static_cast<SessionStorageNamespaceImpl*>(it.second.get());
+    session_storage_namespace_map_[it.first] = source_namespace->Clone();
   }
 
   FinishRestore(source->last_committed_entry_index_, RestoreType::kRestored);
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index 9e491096..ee8c3b8 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/files/scoped_temp_dir.h"
@@ -79,7 +81,7 @@
         did_call_will_redirect_(std::move(did_call_will_redirect)),
         did_call_will_fail_(std::move(did_call_will_fail)),
         did_call_will_process_(std::move(did_call_will_process)) {}
-  ~TestNavigationThrottle() override {}
+  ~TestNavigationThrottle() override = default;
 
   const char* GetNameForLogging() override { return "TestNavigationThrottle"; }
 
@@ -176,7 +178,7 @@
         will_fail_result_(will_fail_result),
         will_process_result_(will_process_result),
         expected_start_url_(expected_start_url) {}
-  ~TestNavigationThrottleInstaller() override {}
+  ~TestNavigationThrottleInstaller() override = default;
 
   // Installs a TestNavigationThrottle whose |method| method will return
   // |result|. All other methods will return NavigationThrottle::PROCEED.
@@ -393,7 +395,7 @@
 // Records all navigation start URLs from the WebContents.
 class NavigationStartUrlRecorder : public WebContentsObserver {
  public:
-  NavigationStartUrlRecorder(WebContents* web_contents)
+  explicit NavigationStartUrlRecorder(WebContents* web_contents)
       : WebContentsObserver(web_contents) {}
 
   void DidStartNavigation(NavigationHandle* navigation_handle) override {
@@ -1069,16 +1071,17 @@
     std::unique_ptr<TestNavigationThrottleInstaller>
         subframe_throttle_installer;
     if (test_case.deferred_block) {
-      subframe_throttle_installer.reset(
-          new TestDeferringNavigationThrottleInstaller(
+      subframe_throttle_installer =
+          std::make_unique<TestDeferringNavigationThrottleInstaller>(
               shell()->web_contents(), test_case.will_start_result,
               test_case.will_redirect_result, NavigationThrottle::PROCEED,
-              NavigationThrottle::PROCEED, blocked_subframe_url));
+              NavigationThrottle::PROCEED, blocked_subframe_url);
     } else {
-      subframe_throttle_installer.reset(new TestNavigationThrottleInstaller(
-          shell()->web_contents(), test_case.will_start_result,
-          test_case.will_redirect_result, NavigationThrottle::PROCEED,
-          NavigationThrottle::PROCEED, blocked_subframe_url));
+      subframe_throttle_installer =
+          std::make_unique<TestNavigationThrottleInstaller>(
+              shell()->web_contents(), test_case.will_start_result,
+              test_case.will_redirect_result, NavigationThrottle::PROCEED,
+              NavigationThrottle::PROCEED, blocked_subframe_url);
     }
 
     {
@@ -1709,7 +1712,7 @@
 // Record and list the navigations that are started and finished.
 class NavigationLogger : public WebContentsObserver {
  public:
-  NavigationLogger(WebContents* web_contents)
+  explicit NavigationLogger(WebContents* web_contents)
       : WebContentsObserver(web_contents) {}
 
   void DidStartNavigation(NavigationHandle* navigation_handle) override {
@@ -2910,7 +2913,7 @@
 
 class TestMixedContentWebContentsDelegate : public WebContentsDelegate {
  public:
-  TestMixedContentWebContentsDelegate() {}
+  TestMixedContentWebContentsDelegate() = default;
   TestMixedContentWebContentsDelegate(
       const TestMixedContentWebContentsDelegate&) = delete;
   TestMixedContentWebContentsDelegate& operator=(
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 77a898c..49b925d 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -35,7 +35,7 @@
   DeletingNavigationThrottle(NavigationHandle* handle,
                              const base::RepeatingClosure& deletion_callback)
       : NavigationThrottle(handle), deletion_callback_(deletion_callback) {}
-  ~DeletingNavigationThrottle() override {}
+  ~DeletingNavigationThrottle() override = default;
 
   NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
     deletion_callback_.Run();
@@ -67,9 +67,7 @@
 
 class NavigationRequestTest : public RenderViewHostImplTestHarness {
  public:
-  NavigationRequestTest()
-      : was_callback_called_(false),
-        callback_result_(NavigationThrottle::DEFER) {}
+  NavigationRequestTest() : callback_result_(NavigationThrottle::DEFER) {}
 
   void SetUp() override {
     RenderViewHostImplTestHarness::SetUp();
@@ -227,7 +225,7 @@
   }
 
   std::unique_ptr<NavigationRequest> request_;
-  bool was_callback_called_;
+  bool was_callback_called_ = false;
   NavigationThrottle::ThrottleCheckResult callback_result_;
 };
 
@@ -452,9 +450,10 @@
 class GetRenderFrameHostOnFailureNavigationThrottle
     : public NavigationThrottle {
  public:
-  GetRenderFrameHostOnFailureNavigationThrottle(NavigationHandle* handle)
+  explicit GetRenderFrameHostOnFailureNavigationThrottle(
+      NavigationHandle* handle)
       : NavigationThrottle(handle) {}
-  ~GetRenderFrameHostOnFailureNavigationThrottle() override {}
+  ~GetRenderFrameHostOnFailureNavigationThrottle() override = default;
 
   NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
     EXPECT_TRUE(navigation_handle()->GetRenderFrameHost());
diff --git a/content/browser/renderer_host/navigation_throttle_runner.cc b/content/browser/renderer_host/navigation_throttle_runner.cc
index 4e6fc79..8f5d6d6 100644
--- a/content/browser/renderer_host/navigation_throttle_runner.cc
+++ b/content/browser/renderer_host/navigation_throttle_runner.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/renderer_host/navigation_throttle_runner.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "content/browser/devtools/devtools_instrumentation.h"
 #include "content/browser/portal/portal_navigation_throttle.h"
 #include "content/browser/prerender/prerender_navigation_throttle.h"
@@ -59,6 +60,41 @@
   return "";
 }
 
+const char* GetEventNameForHistogram(NavigationThrottleRunner::Event event) {
+  switch (event) {
+    case NavigationThrottleRunner::Event::WillStartRequest:
+      return "WillStartRequest";
+    case NavigationThrottleRunner::Event::WillRedirectRequest:
+      return "WillRedirectRequest";
+    case NavigationThrottleRunner::Event::WillFailRequest:
+      return "WillFailRequest";
+    case NavigationThrottleRunner::Event::WillProcessResponse:
+      return "WillProcessResponse";
+    default:
+      NOTREACHED();
+  }
+  return "";
+}
+
+void RecordHistogram(NavigationThrottleRunner::Event event,
+                     base::Time start,
+                     const std::string& metric_type) {
+  base::TimeDelta delta = base::Time::Now() - start;
+  base::UmaHistogramTimes(base::StrCat({"Navigation.Throttle", metric_type, ".",
+                                        GetEventNameForHistogram(event)}),
+                          delta);
+}
+
+void RecordDeferTimeHistogram(NavigationThrottleRunner::Event event,
+                              base::Time start) {
+  RecordHistogram(event, start, "DeferTime");
+}
+
+void RecordExecutionTimeHistogram(NavigationThrottleRunner::Event event,
+                                  base::Time start) {
+  RecordHistogram(event, start, "ExecutionTime");
+}
+
 }  // namespace
 
 NavigationThrottleRunner::NavigationThrottleRunner(Delegate* delegate,
@@ -77,6 +113,7 @@
 void NavigationThrottleRunner::ResumeProcessingNavigationEvent(
     NavigationThrottle* deferring_throttle) {
   DCHECK_EQ(GetDeferringThrottle(), deferring_throttle);
+  RecordDeferTimeHistogram(current_event_, defer_start_time_);
   ProcessInternal();
 }
 
@@ -180,6 +217,7 @@
         "navigation", GetEventName(current_event_), local_navigation_id,
         "throttle", throttles_[i]->GetNameForLogging());
 
+    base::Time start = base::Time::Now();
     NavigationThrottle::ThrottleCheckResult result =
         ExecuteNavigationEvent(throttles_[i].get(), current_event_);
     if (!weak_ref) {
@@ -189,6 +227,7 @@
                                       "result", "deleted");
       return;
     }
+    RecordExecutionTimeHistogram(current_event_, start);
     TRACE_EVENT_NESTABLE_ASYNC_END1("navigation", GetEventName(current_event_),
                                     local_navigation_id, "result",
                                     result.action());
@@ -208,6 +247,7 @@
 
       case NavigationThrottle::DEFER:
         next_index_ = i + 1;
+        defer_start_time_ = base::Time::Now();
         return;
     }
   }
diff --git a/content/browser/renderer_host/navigation_throttle_runner.h b/content/browser/renderer_host/navigation_throttle_runner.h
index 70265c9..993d66c 100644
--- a/content/browser/renderer_host/navigation_throttle_runner.h
+++ b/content/browser/renderer_host/navigation_throttle_runner.h
@@ -82,6 +82,9 @@
   // with.
   const int64_t navigation_id_;
 
+  // The time a throttle started deferring the navigation.
+  base::Time defer_start_time_;
+
   // The event currently being processed.
   Event current_event_ = Event::NoEvent;
   base::WeakPtrFactory<NavigationThrottleRunner> weak_factory_{this};
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 0e02613..a67d156 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1314,8 +1314,8 @@
   // navigation callback and if returning false, will return straight away.
   class CommitCallbackInterceptor {
    public:
-    CommitCallbackInterceptor() {}
-    virtual ~CommitCallbackInterceptor() {}
+    CommitCallbackInterceptor() = default;
+    virtual ~CommitCallbackInterceptor() = default;
 
     virtual bool WillProcessDidCommitNavigation(
         NavigationRequest* navigation_request,
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index d3cc62fa..83ee17ce 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -2665,7 +2665,7 @@
       // Before creating a new RenderFrameProxyHost, ensure a RenderViewHost
       // exists for |instance|, as it creates the page level structure in Blink.
       render_view_host = frame_tree_node_->frame_tree()->CreateRenderViewHost(
-          instance, /*frame_routing_id=*/MSG_ROUTING_NONE,
+          instance, /*main_frame_routing_id=*/MSG_ROUTING_NONE,
           /*swapped_out=*/true, /*renderer_initiated_creation=*/false);
     }
     proxy = CreateRenderFrameProxyHost(instance, std::move(render_view_host));
@@ -3580,7 +3580,7 @@
   if (!CreateSpeculativeRenderFrameHost(
           current_frame_host()->GetSiteInstance(),
           current_frame_host()->GetParent()->GetSiteInstance(),
-          /*for_early_commit=*/false)) {
+          /*recovering_without_early_commit=*/false)) {
     NotifyPrepareForInnerDelegateAttachComplete(false /* success */);
     return;
   }
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
index e10e87b..2fc3ec8 100644
--- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -160,7 +160,7 @@
         message_loop_runner_(new MessageLoopRunner),
         deleted_(false),
         render_frame_host_(rfh) {}
-  ~RenderFrameHostDestructionObserver() override {}
+  ~RenderFrameHostDestructionObserver() override = default;
 
   bool deleted() const { return deleted_; }
 
@@ -2253,7 +2253,7 @@
  public:
   explicit RenderViewHostDestructionObserver(WebContents* web_contents)
       : WebContentsObserver(web_contents) {}
-  ~RenderViewHostDestructionObserver() override {}
+  ~RenderViewHostDestructionObserver() override = default;
   void EnsureRVHGetsDestructed(RenderViewHost* rvh) {
     watched_render_view_hosts_.insert(rvh);
   }
@@ -2495,7 +2495,7 @@
 
 class RFHMProcessPerTabTest : public RenderFrameHostManagerTest {
  public:
-  RFHMProcessPerTabTest() {}
+  RFHMProcessPerTabTest() = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(switches::kProcessPerTab);
@@ -7641,7 +7641,7 @@
 class RenderFrameHostManagerUnloadBrowserTest
     : public RenderFrameHostManagerTest {
  public:
-  RenderFrameHostManagerUnloadBrowserTest() {}
+  RenderFrameHostManagerUnloadBrowserTest() = default;
 
   // Starts monitoring requests made to the embedded_http_server() looking for
   // one made to |url|.  To be used together with WaitForMonitoredRequest().
@@ -7658,7 +7658,7 @@
     if (saw_request_url_)
       return;
 
-    run_loop_.reset(new base::RunLoop());
+    run_loop_ = std::make_unique<base::RunLoop>();
     {
       base::RunLoop* run_loop = run_loop_.get();
       base::AutoUnlock unlock(lock_);
@@ -8023,7 +8023,7 @@
 // out of scope.
 class AssertForegroundHelper {
  public:
-  AssertForegroundHelper() {}
+  AssertForegroundHelper() = default;
 
 #if defined(OS_MAC)
   // Asserts that |renderer_process| isn't backgrounded and reposts self to
@@ -8524,7 +8524,7 @@
     feature_list_.InitAndEnableFeature(
         features::kProcessSharingWithStrictSiteInstances);
   }
-  ~RenderFrameHostManagerDefaultProcessTest() override {}
+  ~RenderFrameHostManagerDefaultProcessTest() override = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     RenderFrameHostManagerTest::SetUpCommandLine(command_line);
diff --git a/content/browser/renderer_host/render_frame_proxy_host.cc b/content/browser/renderer_host/render_frame_proxy_host.cc
index 1df6108..5011974 100644
--- a/content/browser/renderer_host/render_frame_proxy_host.cc
+++ b/content/browser/renderer_host/render_frame_proxy_host.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/renderer_host/render_frame_proxy_host.h"
 
+#include <memory>
 #include <unordered_map>
 #include <utility>
 #include <vector>
@@ -180,7 +181,8 @@
     // parent. The same CrossProcessFrameConnector is used for subsequent cross-
     // process navigations, but it will be destroyed if the frame is
     // navigated back to the same SiteInstance as its parent.
-    cross_process_frame_connector_.reset(new CrossProcessFrameConnector(this));
+    cross_process_frame_connector_ =
+        std::make_unique<CrossProcessFrameConnector>(this);
   }
 
   if (!GetProxyHostCreatedCallback().is_null())
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 20c2816..9f4dc39 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -183,8 +183,7 @@
   ~RenderWidgetHostIteratorImpl() override = default;
 
   void Add(RenderWidgetHost* host) {
-    hosts_.push_back(
-        RenderWidgetHostID(host->GetProcess()->GetID(), host->GetRoutingID()));
+    hosts_.emplace_back(host->GetProcess()->GetID(), host->GetRoutingID());
   }
 
   // RenderWidgetHostIterator:
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 341fd0d..b02a3ac 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -47,9 +47,18 @@
 
 namespace content {
 
+namespace {
+
 const char kBackgroundTracingConfig[] = "config";
 const char kBackgroundTracingUploadUrl[] = "upload_url";
 
+}  // namespace
+
+// static
+BackgroundTracingManager* BackgroundTracingManager::GetInstance() {
+  return BackgroundTracingManagerImpl::GetInstance();
+}
+
 // static
 void BackgroundTracingManagerImpl::RecordMetric(Metrics metric) {
   UMA_HISTOGRAM_ENUMERATION("Tracing.Background.ScenarioState", metric,
@@ -57,11 +66,6 @@
 }
 
 // static
-BackgroundTracingManager* BackgroundTracingManager::GetInstance() {
-  return BackgroundTracingManagerImpl::GetInstance();
-}
-
-// static
 BackgroundTracingManagerImpl* BackgroundTracingManagerImpl::GetInstance() {
   static base::NoDestructor<BackgroundTracingManagerImpl> manager;
   return manager.get();
@@ -409,7 +413,8 @@
 }
 
 BackgroundTracingManagerImpl::TriggerHandle
-BackgroundTracingManagerImpl::RegisterTriggerType(const char* trigger_name) {
+BackgroundTracingManagerImpl::RegisterTriggerType(
+    base::StringPiece trigger_name) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   trigger_handle_ids_ += 1;
@@ -424,8 +429,8 @@
   return trigger_handles_.find(handle) != trigger_handles_.end();
 }
 
-std::string BackgroundTracingManagerImpl::GetTriggerNameFromHandle(
-    BackgroundTracingManager::TriggerHandle handle) const {
+const std::string& BackgroundTracingManagerImpl::GetTriggerNameFromHandle(
+    BackgroundTracingManager::TriggerHandle handle) {
   CHECK(IsTriggerHandleValid(handle));
   return trigger_handles_.find(handle)->second;
 }
diff --git a/content/browser/tracing/background_tracing_manager_impl.h b/content/browser/tracing/background_tracing_manager_impl.h
index 4c6b78dc..358e001 100644
--- a/content/browser/tracing/background_tracing_manager_impl.h
+++ b/content/browser/tracing/background_tracing_manager_impl.h
@@ -105,8 +105,9 @@
 
   // Named triggers
   void TriggerNamedEvent(TriggerHandle, StartedFinalizingCallback) override;
-  TriggerHandle RegisterTriggerType(const char* trigger_name) override;
-  std::string GetTriggerNameFromHandle(TriggerHandle handle) const;
+  TriggerHandle RegisterTriggerType(base::StringPiece trigger_name) override;
+  const std::string& GetTriggerNameFromHandle(
+      TriggerHandle trigger_handle) override;
 
   void OnHistogramTrigger(const std::string& histogram_name);
 
diff --git a/content/browser/web_database/web_database_host_impl.cc b/content/browser/web_database/web_database_host_impl.cc
index c51dc58..8bd536c 100644
--- a/content/browser/web_database/web_database_host_impl.cc
+++ b/content/browser/web_database/web_database_host_impl.cc
@@ -192,29 +192,6 @@
   std::move(callback).Run(attributes);
 }
 
-void WebDatabaseHostImpl::GetFileSize(const std::u16string& vfs_file_name,
-                                      GetFileSizeCallback callback) {
-  DCHECK(db_tracker_->task_runner()->RunsTasksInCurrentSequence());
-  ValidateOrigin(vfs_file_name,
-                 base::BindOnce(&WebDatabaseHostImpl::GetFileSizeValidated,
-                                weak_ptr_factory_.GetWeakPtr(), vfs_file_name,
-                                std::move(callback)));
-}
-
-void WebDatabaseHostImpl::GetFileSizeValidated(
-    const std::u16string& vfs_file_name,
-    GetFileSizeCallback callback) {
-  DCHECK(db_tracker_->task_runner()->RunsTasksInCurrentSequence());
-
-  int64_t size = 0LL;
-  base::FilePath db_file =
-      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
-  if (!db_file.empty()) {
-    size = VfsBackend::GetFileSize(db_file);
-  }
-  std::move(callback).Run(size);
-}
-
 void WebDatabaseHostImpl::SetFileSize(const std::u16string& vfs_file_name,
                                       int64_t expected_size,
                                       SetFileSizeCallback callback) {
diff --git a/content/browser/web_database/web_database_host_impl.h b/content/browser/web_database/web_database_host_impl.h
index 9c004d3..c2289e74 100644
--- a/content/browser/web_database/web_database_host_impl.h
+++ b/content/browser/web_database/web_database_host_impl.h
@@ -51,9 +51,6 @@
   void GetFileAttributes(const std::u16string& vfs_file_name,
                          GetFileAttributesCallback callback) override;
 
-  void GetFileSize(const std::u16string& vfs_file_name,
-                   GetFileSizeCallback callback) override;
-
   void SetFileSize(const std::u16string& vfs_file_name,
                    int64_t expected_size,
                    SetFileSizeCallback callback) override;
@@ -103,9 +100,6 @@
   void GetFileAttributesValidated(const std::u16string& vfs_file_name,
                                   GetFileAttributesCallback callback);
 
-  void GetFileSizeValidated(const std::u16string& vfs_file_name,
-                            GetFileSizeCallback callback);
-
   void SetFileSizeValidated(const std::u16string& vfs_file_name,
                             int64_t expected_size,
                             SetFileSizeCallback callback);
diff --git a/content/browser/web_database/web_database_host_impl_unittest.cc b/content/browser/web_database/web_database_host_impl_unittest.cc
index 978e2cad..2451f72 100644
--- a/content/browser/web_database/web_database_host_impl_unittest.cc
+++ b/content/browser/web_database/web_database_host_impl_unittest.cc
@@ -149,9 +149,6 @@
     host()->GetFileAttributes(bad_vfs_file_name, base::DoNothing());
   });
 
-  CheckUnauthorizedOrigin(
-      [&]() { host()->GetFileSize(bad_vfs_file_name, base::DoNothing()); });
-
   CheckUnauthorizedOrigin([&]() {
     host()->SetFileSize(bad_vfs_file_name, /*expected_size=*/0,
                         base::DoNothing());
diff --git a/content/browser/web_package/subresource_loading_origin_trial.md b/content/browser/web_package/subresource_loading_origin_trial.md
new file mode 100644
index 0000000..adc60a0
--- /dev/null
+++ b/content/browser/web_package/subresource_loading_origin_trial.md
@@ -0,0 +1,123 @@
+# Origin Trial for Subresource Loading with Web Bundles
+
+This document is for web developers who want to participate in Origin Trial for
+[Subresource Loading with Web Bundles][explainer].
+
+- [Chrome Status]
+- [Explainer]
+- [Intent to Experiment](https://groups.google.com/a/chromium.org/g/blink-dev/c/9CwkzaF_eQ4/m/kuR07FTTCAAJ)
+- [Registration Form](https://developer.chrome.com/origintrials/#/view_trial/-6307291278132379647)
+
+## Origin Trial timeline
+
+Chrome M90-M92.
+
+## How to create a bundle
+
+There are several tools available.
+
+- Go: [gen-bundle](https://github.com/WICG/webpackage/tree/master/go/bundle)
+  tool in the WICG/webpackage repository.
+- npm: [wbn](https://www.npmjs.com/package/wbn)
+
+## What works in Chrome M90 or later.
+
+Chrome M90 or later supports `<link>`-based API explained in the [Explainer]. In
+addition to `resources` attribute, `scopes` attribute is also supported.
+
+### Examples
+
+Using `resources` attribute:
+
+```html
+<link
+  rel="webbundle"
+  href="https://example.com/dir/subresources.wbn"
+  resources="https://example.com/dir/a.js https://example.com/dir/b.js https://example.com/dir/c.png"
+/>
+```
+
+Using `scopes` attribute:
+
+```html
+<link
+  rel="webbundle"
+  href="https://example.com/dir/subresources.wbn"
+  scopes="https://example.com/dir/js/
+          https://example.com/dir/img/
+          https://example.com/dir/css/"
+/>
+```
+
+Using both `resources` and `scopes` attribute also works:
+
+```html
+<link
+  rel="webbundle"
+  href="https://example.com/dir/subresources.wbn"
+  resources="https://example.com/dir/a.js https://example.com/dir/b.js"
+  scopes="https://example.com/dir/js/
+          https://example.com/dir/img/
+          https://example.com/dir/css/"
+/>
+```
+
+A `urn:uuid` URL is also supported:
+
+```html
+<link
+  rel="webbundle"
+  href="https://example.com/dir/subresources.wbn"
+  resources="urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
+/>
+
+<iframe src="urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"></iframe>
+```
+
+## Feature detection
+
+You can use
+[`HTMLLinkElement.relList`](https://html.spec.whatwg.org/multipage/semantics.html#dom-link-rellist)
+for feature detection.
+
+```js
+const link = document.createElement("link");
+if (link.relList.supports("webbundle")) {
+   // Supported
+   ...
+} else {
+   // Unsupported
+   ...
+}
+```
+
+## Feature detection for `scopes` (which is available in M90 or later)
+
+If you want to make sure you can use `scopes` attribute, the following should
+work:
+
+```js
+if ("scopes" in HTMLLinkElement.prototype) {
+  // `scopes` attribute is supported. Chrome is in M90 or later.
+  ...
+} else {
+  // `scopes` attribute is not supported. Chrome is in M89 or earlier.
+  ...
+}
+```
+
+A `resources` attribute is always supported if
+`link.relList.supports("webbundle")` is true.
+
+Chrome M89 will show `ExternalProtolaDialog` for a iframe loading with
+`urn:uuid` URL. You should check Chrome is M90 or later to avoid that.
+
+# How to try this feature locally
+
+Enable _Experimental Web Platform Features_ flag
+([chrome://flags/#enable-experimental-web-platform-features](chrome://flags/#enable-experimental-web-platform-features)).
+Note that an earlier version of Chrome might not support this feature.
+
+[chrome status]: https://www.chromestatus.com/feature/5710618575241216
+[explainer]:
+  https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md
diff --git a/content/browser/webrtc/webrtc_getusermedia_browsertest.cc b/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
index 331cc1c4..955f3b5 100644
--- a/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
+++ b/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
@@ -760,8 +760,9 @@
   ExecuteJavascriptAndWaitForOk("concurrentGetUserMediaStop()");
 }
 
+// TODO(crbug.com/1087081) : Flaky on all platforms.
 IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest,
-                       GetUserMediaAfterStopElementCapture) {
+                       DISABLED_GetUserMediaAfterStopElementCapture) {
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -776,8 +777,9 @@
   ExecuteJavascriptAndWaitForOk("getUserMediaEchoCancellationOnAndOff()");
 }
 
+// TODO(crbug.com/1087081) : Flaky on all platforms.
 IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest,
-                       GetUserMediaEchoCancellationOnAndOffAndVideo) {
+                       DISABLED_GetUserMediaEchoCancellationOnAndOffAndVideo) {
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url));
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
index fe9f772..67f76d8 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
@@ -19,7 +19,7 @@
  * Ranking of ChildProcessConnections for a particular ChildConnectionAllocator.
  */
 public class ChildProcessRanking implements Iterable<ChildProcessConnection> {
-    private static final boolean ENABLE_CHECKS = BuildConfig.DCHECK_IS_ON;
+    private static final boolean ENABLE_CHECKS = BuildConfig.ENABLE_ASSERTS;
     private static final int NO_GROUP = 0;
     private static final int LOW_RANK_GROUP = 1;
 
diff --git a/content/public/browser/android/compositor.h b/content/public/browser/android/compositor.h
index 5081b458..6dac53d 100644
--- a/content/public/browser/android/compositor.h
+++ b/content/public/browser/android/compositor.h
@@ -105,6 +105,10 @@
   // Evicts the cache entry created from the cached call above.
   virtual void EvictCachedBackBuffer() = 0;
 
+  // Notifies associated Display to not detach child surface controls during
+  // destruction.
+  virtual void PreserveChildSurfaceControls() = 0;
+
   // Registers a callback that is run when the next frame successfully makes it
   // to the screen (it's entirely possible some frames may be dropped between
   // the time this is called and the callback is run).
diff --git a/content/public/browser/background_tracing_manager.h b/content/public/browser/background_tracing_manager.h
index a55899d..a964058a 100644
--- a/content/public/browser/background_tracing_manager.h
+++ b/content/public/browser/background_tracing_manager.h
@@ -87,7 +87,11 @@
 
   // Registers a manual trigger handle, and returns a TriggerHandle which can
   // be passed to DidTriggerHappen().
-  virtual TriggerHandle RegisterTriggerType(const char* trigger_name) = 0;
+  virtual TriggerHandle RegisterTriggerType(base::StringPiece trigger_name) = 0;
+
+  // Returns the name associated with the given trigger handle.
+  virtual const std::string& GetTriggerNameFromHandle(
+      TriggerHandle trigger_handle) = 0;
 
   virtual bool HasActiveScenario() = 0;
 
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index b4e1563a..290e65b 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -9,7 +9,7 @@
 // declarations instead of including more headers. If that is infeasible, adjust
 // the limit. For more info, see
 // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/wmax_tokens.md
-#pragma clang max_tokens_here 852500
+#pragma clang max_tokens_here 853000
 
 #include <utility>
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 81e1a34..7d52afb 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -230,7 +230,7 @@
 // media-device enumeration will provide at most one device per type and the
 // device IDs will not be available.
 // TODO(crbug.com/1019176): remove the feature in M89.
-const base::Feature kEnumerateDevicesHideDeviceIDs{
+const base::Feature kEnumerateDevicesHideDeviceIDs {
   "EnumerateDevicesHideDeviceIDs",
 #if defined(OS_ANDROID)
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -401,7 +401,7 @@
     {MBIMode::kLegacy, "legacy"},
     {MBIMode::kEnabledPerRenderProcessHost, "per_render_process_host"},
     {MBIMode::kEnabledPerSiteInstance, "per_site_instance"}};
-const base::FeatureParam<MBIMode> kMBIModeParam{
+const base::FeatureParam<MBIMode> kMBIModeParam {
   &kMBIMode, "mode",
 #if BUILDFLAG(MBI_MODE_PER_RENDER_PROCESS_HOST)
       MBIMode::kEnabledPerRenderProcessHost,
@@ -1030,7 +1030,8 @@
 // On ChromeOS the service must run in the browser process, because parts of the
 // code depend on global objects that are only available in the Browser process.
 // See https://crbug.com/891961.
-#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) || \
+    BUILDFLAG(IS_CHROMEOS_LACROS)
   return VideoCaptureServiceConfiguration::kEnabledForBrowserProcess;
 #else
 #if defined(OS_WIN)
diff --git a/content/renderer/accessibility/ax_tree_snapshotter_impl.cc b/content/renderer/accessibility/ax_tree_snapshotter_impl.cc
index f30fea11..e31705b 100644
--- a/content/renderer/accessibility/ax_tree_snapshotter_impl.cc
+++ b/content/renderer/accessibility/ax_tree_snapshotter_impl.cc
@@ -74,6 +74,7 @@
   // this was indeed a partial update to the tree (which we don't want).
   DCHECK_EQ(0, response->node_id_to_clear);
   DCHECK_EQ(ax::mojom::EventFrom::kNone, response->event_from);
+  DCHECK_EQ(ax::mojom::Action::kNone, response->event_from_action);
 }
 
 }  // namespace content
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index b99ba2c..231ca8f 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -283,9 +283,9 @@
     // request. Instead, the mojo reply should be used directly.
     if (event_to_fire != ax::mojom::Event::kNone) {
       const std::vector<ui::AXEventIntent> intents;
-      HandleAXEvent(ui::AXEvent(ax_object.AxID(), event_to_fire,
-                                ax::mojom::EventFrom::kAction, intents,
-                                request_id));
+      HandleAXEvent(ui::AXEvent(
+          ax_object.AxID(), event_to_fire, ax::mojom::EventFrom::kAction,
+          ax::mojom::Action::kHitTest, intents, request_id));
     }
 
     // Reply with the result.
@@ -411,7 +411,8 @@
         CreateAXImageAnnotator();
         // Walk the tree to discover images, and mark them dirty so that
         // they get added to the annotator.
-        MarkAllAXObjectsDirty(ax::mojom::Role::kImage);
+        MarkAllAXObjectsDirty(ax::mojom::Role::kImage,
+                              ax::mojom::Action::kAnnotatePageImages);
       }
       break;
     case ax::mojom::Action::kSignalEndOfTest:
@@ -449,11 +450,14 @@
   HandleAXEvent(event);
 }
 
-void RenderAccessibilityImpl::MarkWebAXObjectDirty(const WebAXObject& obj,
-                                                   bool subtree) {
+void RenderAccessibilityImpl::MarkWebAXObjectDirty(
+    const WebAXObject& obj,
+    bool subtree,
+    ax::mojom::Action event_from_action) {
   DirtyObject dirty_object;
   dirty_object.obj = obj;
   dirty_object.event_from = ax::mojom::EventFrom::kAction;
+  dirty_object.event_from_action = event_from_action;
   dirty_objects_.push_back(dirty_object);
 
   if (subtree)
@@ -890,6 +894,7 @@
         DirtyObject dirty_object;
         dirty_object.obj = obj;
         dirty_object.event_from = event.event_from;
+        dirty_object.event_from_action = event.event_from_action;
         dirty_object.event_intents = event.event_intents;
         dirty_objects.push_back(dirty_object);
       }
@@ -905,6 +910,7 @@
       DirtyObject dirty_object;
       dirty_object.obj = obj;
       dirty_object.event_from = event.event_from;
+      dirty_object.event_from_action = event.event_from_action;
       dirty_object.event_intents = event.event_intents;
       dirty_objects.push_back(dirty_object);
     }
@@ -969,6 +975,7 @@
 
     ui::AXTreeUpdate update;
     update.event_from = dirty_objects[i].event_from;
+    update.event_from_action = dirty_objects[i].event_from_action;
     update.event_intents = dirty_objects[i].event_intents;
     // If there's a plugin, force the tree data to be generated in every
     // message so the plugin can merge its own tree data changes.
@@ -1215,7 +1222,9 @@
   }
 }
 
-void RenderAccessibilityImpl::MarkAllAXObjectsDirty(ax::mojom::Role role) {
+void RenderAccessibilityImpl::MarkAllAXObjectsDirty(
+    ax::mojom::Role role,
+    ax::mojom::Action event_from_action) {
   ScopedFreezeBlinkAXTreeSource freeze(tree_source_.get());
   base::queue<WebAXObject> objs_to_explore;
   objs_to_explore.push(tree_source_->GetRoot());
@@ -1224,7 +1233,7 @@
     objs_to_explore.pop();
 
     if (obj.Role() == role)
-      MarkWebAXObjectDirty(obj, /* subtree */ false);
+      MarkWebAXObjectDirty(obj, /* subtree */ false, event_from_action);
 
     std::vector<blink::WebAXObject> children;
     tree_source_->GetChildren(obj, &children);
diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h
index c8dfaf81..5012e06d 100644
--- a/content/renderer/accessibility/render_accessibility_impl.h
+++ b/content/renderer/accessibility/render_accessibility_impl.h
@@ -110,7 +110,10 @@
 
   // Called when an accessibility notification occurs in Blink.
   void HandleWebAccessibilityEvent(const ui::AXEvent& event);
-  void MarkWebAXObjectDirty(const blink::WebAXObject& obj, bool subtree);
+  void MarkWebAXObjectDirty(
+      const blink::WebAXObject& obj,
+      bool subtree,
+      ax::mojom::Action event_from_action = ax::mojom::Action::kNone);
 
   void HandleAXEvent(const ui::AXEvent& event);
 
@@ -149,6 +152,7 @@
     ~DirtyObject();
     blink::WebAXObject obj;
     ax::mojom::EventFrom event_from;
+    ax::mojom::Action event_from_action;
     std::vector<ui::AXEventIntent> event_intents;
   };
 
@@ -189,7 +193,8 @@
   void StartOrStopLabelingImages(ui::AXMode old_mode, ui::AXMode new_mode);
 
   // Marks all AXObjects with the given role in the current tree dirty.
-  void MarkAllAXObjectsDirty(ax::mojom::Role role);
+  void MarkAllAXObjectsDirty(ax::mojom::Role role,
+                             ax::mojom::Action event_from_action);
 
   void Scroll(const ui::AXActionTarget* target,
               ax::mojom::Action scroll_action);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index e2425d04..e8744f4 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4691,13 +4691,15 @@
       ->HandleWebAccessibilityEvent(event);
 }
 
-void RenderFrameImpl::MarkWebAXObjectDirty(const blink::WebAXObject& obj,
-                                           bool subtree) {
+void RenderFrameImpl::MarkWebAXObjectDirty(
+    const blink::WebAXObject& obj,
+    bool subtree,
+    ax::mojom::Action event_from_action) {
   if (!IsAccessibilityEnabled())
     return;
 
   render_accessibility_manager_->GetRenderAccessibilityImpl()
-      ->MarkWebAXObjectDirty(obj, subtree);
+      ->MarkWebAXObjectDirty(obj, subtree, event_from_action);
 }
 
 void RenderFrameImpl::AddObserver(RenderFrameObserver* observer) {
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 9ab20dee..b15a0cd 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -610,8 +610,10 @@
   bool AllowContentInitiatedDataUrlNavigations(
       const blink::WebURL& url) override;
   void PostAccessibilityEvent(const ui::AXEvent& event) override;
-  void MarkWebAXObjectDirty(const blink::WebAXObject& obj,
-                            bool subtree) override;
+  void MarkWebAXObjectDirty(
+      const blink::WebAXObject& obj,
+      bool subtree,
+      ax::mojom::Action event_from_action = ax::mojom::Action::kNone) override;
   void CheckIfAudioSinkExistsAndIsAuthorized(
       const blink::WebString& sink_id,
       blink::WebSetSinkIdCompleteCallback callback) override;
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index ed0b740..f5403e9 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -8,7 +8,6 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/command_line.h"
-#include "base/debug/crash_logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
@@ -517,7 +516,6 @@
       return;
     }
     response_head_->encoded_data_length = 0;
-    received_redirect_for_bug1162035_ = true;
     url_loader_client_->OnReceiveRedirect(*redirect_info_,
                                           response_head_.Clone());
     TransitionToStatus(Status::kSentRedirect);
@@ -723,18 +721,7 @@
          "https://crbug.com/845683";
   DCHECK(!new_url.has_value()) << "Redirect with modified url was not "
                                   "supported yet. crbug.com/845683";
-
-  // TODO(crbug.com/1162035): Replace with a DCHECK or early return when
-  // the bug is understood.
-  if (!redirect_info_) {
-    SCOPED_CRASH_KEY_NUMBER("bug1162035", "follow_status",
-                            static_cast<int>(status_));
-    SCOPED_CRASH_KEY_BOOL("bug1162035", "received_redirect",
-                          received_redirect_for_bug1162035_);
-    SCOPED_CRASH_KEY_BOOL("bug1162035", "followed_redirect",
-                          followed_redirect_for_bug1162035_);
-    CHECK(false);
-  }
+  DCHECK(redirect_info_);
 
   bool should_clear_upload = false;
   net::RedirectUtil::UpdateHttpRequest(
@@ -757,7 +744,6 @@
   // Restart the request.
   TransitionToStatus(Status::kNotStarted);
   redirect_info_.reset();
-  followed_redirect_for_bug1162035_ = true;
   response_callback_receiver_.reset();
   StartRequest(resource_request_);
 }
@@ -909,31 +895,25 @@
 }
 
 void ServiceWorkerSubresourceLoader::TransitionToStatus(Status new_status) {
-  // TODO(crbug.com/1162035): Remove once the bug is understood and replace
-  // the CHECKs below to DCHECKs.
-  SCOPED_CRASH_KEY_NUMBER("bug1162035", "transition_old",
-                          static_cast<int>(status_));
-  SCOPED_CRASH_KEY_NUMBER("bug1162035", "transition_new",
-                          static_cast<int>(new_status));
-
+#if DCHECK_IS_ON()
   switch (new_status) {
     case Status::kNotStarted:
-      CHECK_EQ(status_, Status::kSentRedirect);
+      DCHECK_EQ(status_, Status::kSentRedirect);
       break;
     case Status::kStarted:
-      CHECK_EQ(status_, Status::kNotStarted);
+      DCHECK_EQ(status_, Status::kNotStarted);
       break;
     case Status::kSentRedirect:
-      CHECK_EQ(status_, Status::kStarted);
+      DCHECK_EQ(status_, Status::kStarted);
       break;
     case Status::kSentHeader:
-      CHECK_EQ(status_, Status::kStarted);
+      DCHECK_EQ(status_, Status::kStarted);
       break;
     case Status::kSentBody:
-      CHECK_EQ(status_, Status::kSentHeader);
+      DCHECK_EQ(status_, Status::kSentHeader);
       break;
     case Status::kCompleted:
-      CHECK(
+      DCHECK(
           // Network fallback before interception.
           status_ == Status::kNotStarted ||
           // Network fallback after interception.
@@ -944,6 +924,7 @@
           status_ == Status::kSentBody);
       break;
   }
+#endif  // DCHECK_IS_ON()
 
   status_ = new_status;
 }
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.h b/content/renderer/service_worker/service_worker_subresource_loader.h
index 636b37e..fc5a133 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.h
+++ b/content/renderer/service_worker/service_worker_subresource_loader.h
@@ -206,11 +206,6 @@
   blink::mojom::ServiceWorkerFetchEventTimingPtr fetch_event_timing_;
   network::mojom::FetchResponseSource response_source_;
 
-  // For debugging crbug.com/1162035. Set to true after a redirect is
-  // received/followed.
-  bool received_redirect_for_bug1162035_ = false;
-  bool followed_redirect_for_bug1162035_ = false;
-
   base::WeakPtrFactory<ServiceWorkerSubresourceLoader> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSubresourceLoader);
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index 2cec3e4..28a6bc70 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -89,3 +89,6 @@
 
 # Flaky on Fuchsia
 crbug.com/1087298 [ fuchsia ] GpuProcess_webgl_disabled_extension [ Skip ]
+
+# Flakily hits a DCHECK during shared image creation.
+crbug.com/1188437 [ linux intel display-server-wayland ] GpuProcess_webgl [ RetryOnFailure ]
diff --git a/content/web_test/renderer/web_frame_test_proxy.cc b/content/web_test/renderer/web_frame_test_proxy.cc
index 9fda0c6d..92650404 100644
--- a/content/web_test/renderer/web_frame_test_proxy.cc
+++ b/content/web_test/renderer/web_frame_test_proxy.cc
@@ -643,8 +643,10 @@
   RenderFrameImpl::PostAccessibilityEvent(event);
 }
 
-void WebFrameTestProxy::MarkWebAXObjectDirty(const blink::WebAXObject& object,
-                                             bool subtree) {
+void WebFrameTestProxy::MarkWebAXObjectDirty(
+    const blink::WebAXObject& object,
+    bool subtree,
+    ax::mojom::Action event_from_action) {
   HandleWebAccessibilityEvent(object, "MarkDirty",
                               std::vector<ui::AXEventIntent>());
 
@@ -654,7 +656,7 @@
   if (object.IsDetached())
     return;  // |this| is invalid.
 
-  RenderFrameImpl::MarkWebAXObjectDirty(object, subtree);
+  RenderFrameImpl::MarkWebAXObjectDirty(object, subtree, event_from_action);
 }
 
 void WebFrameTestProxy::HandleWebAccessibilityEvent(
diff --git a/content/web_test/renderer/web_frame_test_proxy.h b/content/web_test/renderer/web_frame_test_proxy.h
index 3c4fc26..f6da964 100644
--- a/content/web_test/renderer/web_frame_test_proxy.h
+++ b/content/web_test/renderer/web_frame_test_proxy.h
@@ -75,8 +75,10 @@
                        ForRedirect for_redirect) override;
   void BeginNavigation(std::unique_ptr<blink::WebNavigationInfo> info) override;
   void PostAccessibilityEvent(const ui::AXEvent& event) override;
-  void MarkWebAXObjectDirty(const blink::WebAXObject& object,
-                            bool subtree) override;
+  void MarkWebAXObjectDirty(
+      const blink::WebAXObject& object,
+      bool subtree,
+      ax::mojom::Action event_from_action = ax::mojom::Action::kNone) override;
   void CheckIfAudioSinkExistsAndIsAuthorized(
       const blink::WebString& sink_id,
       blink::WebSetSinkIdCompleteCallback completion_callback) override;
diff --git a/extensions/browser/api/system_cpu/BUILD.gn b/extensions/browser/api/system_cpu/BUILD.gn
index a6e6304..9f907cf 100644
--- a/extensions/browser/api/system_cpu/BUILD.gn
+++ b/extensions/browser/api/system_cpu/BUILD.gn
@@ -34,7 +34,7 @@
     sources += [ "cpu_info_provider_win.cc" ]
   }
 
-  if (is_chromeos_ash) {
+  if (is_chromeos) {
     deps += [ "//chromeos/system" ]
   }
 
diff --git a/extensions/browser/api/system_cpu/cpu_info_provider.cc b/extensions/browser/api/system_cpu/cpu_info_provider.cc
index 0814a821..ba72c04 100644
--- a/extensions/browser/api/system_cpu/cpu_info_provider.cc
+++ b/extensions/browser/api/system_cpu/cpu_info_provider.cc
@@ -7,9 +7,9 @@
 #include "base/system/sys_info.h"
 #include "build/chromeos_buildflags.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/system/cpu_temperature_reader.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 
 namespace extensions {
 
@@ -45,7 +45,7 @@
   if (!QueryCpuTimePerProcessor(&info_.processors))
     info_.processors.clear();
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
   using CPUTemperatureInfo =
       chromeos::system::CPUTemperatureReader::CPUTemperatureInfo;
   std::vector<CPUTemperatureInfo> cpu_temp_info =
@@ -55,7 +55,7 @@
   for (const CPUTemperatureInfo& info : cpu_temp_info) {
     info_.temperatures.push_back(info.temp_celsius);
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
 
   return true;
 }
diff --git a/extensions/common/api/bluetooth.idl b/extensions/common/api/bluetooth.idl
index 6b0c953..a1703762 100644
--- a/extensions/common/api/bluetooth.idl
+++ b/extensions/common/api/bluetooth.idl
@@ -133,12 +133,14 @@
     // Get information about the Bluetooth adapter.
     // |callback| : Called with an AdapterState object describing the adapter
     // state.
-    static void getAdapterState(AdapterStateCallback callback);
+    [supportsPromises] static void getAdapterState(
+        AdapterStateCallback callback);
 
     // Get information about a Bluetooth device known to the system.
     // |deviceAddress| : Address of device to get.
     // |callback| : Called with the Device object describing the device.
-    static void getDevice(DOMString deviceAddress, GetDeviceCallback callback);
+    [supportsPromises] static void getDevice(DOMString deviceAddress,
+                                             GetDeviceCallback callback);
 
     // Get a list of Bluetooth devices known to the system, including paired
     // and recently discovered devices.
@@ -147,8 +149,8 @@
     // will contain all bluetooth devices. Right now this is only supported in
     // ChromeOS, for other platforms, a full list is returned.
     // |callback| : Called when the search is completed.
-    static void getDevices(optional BluetoothFilter filter,
-                           GetDevicesCallback callback);
+    [supportsPromises] static void getDevices(optional BluetoothFilter filter,
+                                              GetDevicesCallback callback);
 
     // Start discovery. Newly discovered devices will be returned via the
     // onDeviceAdded event. Previously discovered devices already known to
@@ -159,11 +161,13 @@
     // startDiscovery.  Discovery can be resource intensive: stopDiscovery
     // should be called as soon as possible.
     // |callback| : Called to indicate success or failure.
-    static void startDiscovery(optional StartDiscoveryCallback callback);
+    [supportsPromises] static void startDiscovery(
+        optional StartDiscoveryCallback callback);
 
     // Stop discovery.
     // |callback| : Called to indicate success or failure.
-    static void stopDiscovery(optional StopDiscoveryCallback callback);
+    [supportsPromises] static void stopDiscovery(
+        optional StopDiscoveryCallback callback);
   };
 
   interface Events {
diff --git a/extensions/common/api/dns.idl b/extensions/common/api/dns.idl
index 0500c0f..dd272f3 100644
--- a/extensions/common/api/dns.idl
+++ b/extensions/common/api/dns.idl
@@ -10,7 +10,7 @@
     long resultCode;
 
     // A string representing the IP address literal. Supplied only if resultCode
-    // indicates success. Note that we presently return only IPv4 addresses.
+    // indicates success.
     DOMString? address;
   };
 
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
index 1740c176..6af4442 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
@@ -133,6 +133,8 @@
     ui::AXEvent generated_event;
     generated_event.id = targeted_event.node->id();
     generated_event.event_from = targeted_event.event_params.event_from;
+    generated_event.event_from_action =
+        targeted_event.event_params.event_from_action;
     generated_event.event_intents = targeted_event.event_params.event_intents;
     owner_->SendAutomationEvent(event_bundle.tree_id,
                                 event_bundle.mouse_location, generated_event,
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
index 4b8a7ca..e67f145f 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
@@ -2507,6 +2507,8 @@
   event_params.SetKey("eventType", base::Value(automation_event_type_str));
 
   event_params.SetKey("eventFrom", base::Value(ui::ToString(event.event_from)));
+  event_params.SetKey("eventFromAction",
+                      base::Value(ui::ToString(event.event_from_action)));
   event_params.SetKey("actionRequestID", base::Value(event.action_request_id));
   event_params.SetKey("mouseX", base::Value(mouse_location.x()));
   event_params.SetKey("mouseY", base::Value(mouse_location.y()));
@@ -2550,6 +2552,7 @@
   // Determine whether there's a focus or blur event and take its event from.
   // Also, save the raw event target (tree + node).
   ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
+  ax::mojom::Action event_from_action = ax::mojom::Action::kNone;
   ui::AXNodeData::AXID raw_focus_target_id = ui::AXNodeData::kInvalidAXID;
   bool event_bundle_has_focus_or_blur = false;
   for (const auto& event : event_bundle.events) {
@@ -2557,6 +2560,7 @@
     bool is_focus = event.event_type == ax::mojom::Event::kFocus;
     if (is_blur || is_focus) {
       event_from = event.event_from;
+      event_from_action = event.event_from_action;
       event_bundle_has_focus_or_blur = true;
     }
 
@@ -2593,6 +2597,7 @@
     ui::AXEvent blur_event;
     blur_event.id = old_node->id();
     blur_event.event_from = event_from;
+    blur_event.event_from_action = event_from_action;
     blur_event.event_type = ax::mojom::Event::kBlur;
     SendAutomationEvent(old_wrapper->GetTreeID(), event_bundle.mouse_location,
                         blur_event);
@@ -2606,6 +2611,7 @@
     ui::AXEvent focus_event;
     focus_event.id = new_node->id();
     focus_event.event_from = event_from;
+    focus_event.event_from_action = event_from_action;
     focus_event.event_type = ax::mojom::Event::kFocus;
     SendAutomationEvent(new_wrapper->GetTreeID(), event_bundle.mouse_location,
                         focus_event);
diff --git a/extensions/renderer/resources/automation/automation_event.js b/extensions/renderer/resources/automation/automation_event.js
index 22e341d..f107941 100644
--- a/extensions/renderer/resources/automation/automation_event.js
+++ b/extensions/renderer/resources/automation/automation_event.js
@@ -4,12 +4,14 @@
 
 var utils = require('utils');
 
-function AutomationEventImpl(type, target, eventFrom, mouseX, mouseY, intents) {
+function AutomationEventImpl(
+    type, target, eventFrom, eventFromAction, mouseX, mouseY, intents) {
   this.propagationStopped = false;
   this.type = type;
   this.target = target;
   this.eventPhase = Event.NONE;
   this.eventFrom = eventFrom;
+  this.eventFromAction = eventFromAction;
   this.mouseX = mouseX;
   this.mouseY = mouseY;
   this.intents = intents;
@@ -37,6 +39,7 @@
     'target',
     'eventPhase',
     'eventFrom',
+    'eventFromAction',
     'mouseX',
     'mouseY',
     'intents',
diff --git a/extensions/renderer/resources/automation/automation_node.js b/extensions/renderer/resources/automation/automation_node.js
index c92fea4..4549ec6 100644
--- a/extensions/renderer/resources/automation/automation_node.js
+++ b/extensions/renderer/resources/automation/automation_node.js
@@ -1113,7 +1113,8 @@
              attributes: this.attributes };
   },
 
-  dispatchEvent: function(eventType, eventFrom, mouseX, mouseY, intents) {
+  dispatchEvent: function(
+      eventType, eventFrom, eventFromAction, mouseX, mouseY, intents) {
     var path = [];
     var parent = this.parent;
     while (parent) {
@@ -1121,8 +1122,9 @@
       parent = parent.parent;
     }
 
-    var event = new AutomationEvent(eventType, this.wrapper, eventFrom, mouseX,
-                                    mouseY, intents);
+    var event = new AutomationEvent(
+        eventType, this.wrapper, eventFrom, eventFromAction, mouseX, mouseY,
+        intents);
 
     // Dispatch the event through the propagation path in three phases:
     // - capturing: starting from the root and going down to the target's parent
@@ -1790,8 +1792,8 @@
     if (targetNode) {
       var targetNodeImpl = privates(targetNode).impl;
       targetNodeImpl.dispatchEvent(
-          eventParams.eventType,
-          eventParams.eventFrom, eventParams.mouseX, eventParams.mouseY,
+          eventParams.eventType, eventParams.eventFrom,
+          eventParams.eventFromAction, eventParams.mouseX, eventParams.mouseY,
           eventParams.intents);
 
       if (eventParams.actionRequestID != -1) {
diff --git a/gpu/config/gpu_test_config.cc b/gpu/config/gpu_test_config.cc
index cc4f0b8..592f7124 100644
--- a/gpu/config/gpu_test_config.cc
+++ b/gpu/config/gpu_test_config.cc
@@ -82,11 +82,7 @@
       }
       break;
     case 11:
-      switch (minor_version) {
-        case 0:
-          return GPUTestConfig::kOsMacBigSur;
-      }
-      break;
+      return GPUTestConfig::kOsMacBigSur;
   }
   return GPUTestConfig::kOsUnknown;
 #elif defined(OS_ANDROID)
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer_unittest.mm b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer_unittest.mm
index a15eed7..032201ac 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/translate/translate_infobar_modal_overlay_request_callback_installer_unittest.mm
@@ -54,6 +54,10 @@
     installer_.InstallCallbacks(request_);
   }
 
+  ~TranslateInfobarModalOverlayRequestCallbackInstallerTest() override {
+    manager()->ShutDown();
+  }
+
   InfoBarManagerImpl* manager() {
     return InfoBarManagerImpl::FromWebState(&web_state_);
   }
diff --git a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
index 76a2251f..9ff1597 100644
--- a/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/translate_overlay_tab_helper_unittest.mm
@@ -63,6 +63,10 @@
         ->AddInfoBar(std::move(infobar));
   }
 
+  ~TranslateInfobarOverlayTranslateOverlayTabHelperTest() override {
+    InfoBarManagerImpl::FromWebState(&web_state_)->ShutDown();
+  }
+
   // Returns the front request of |web_state_|'s OverlayRequestQueue.
   OverlayRequest* front_request() {
     return OverlayRequestQueue::FromWebState(&web_state_,
diff --git a/ios/chrome/browser/ui/settings/language/language_settings_mediator.mm b/ios/chrome/browser/ui/settings/language/language_settings_mediator.mm
index be7260c..7058d1a 100644
--- a/ios/chrome/browser/ui/settings/language/language_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/language/language_settings_mediator.mm
@@ -218,6 +218,7 @@
   _acceptLanguagesPrefObserverBridge.reset();
   _fluentLanguagesPrefObserverBridge.reset();
   _prefChangeRegistrar.reset();
+  _translatePrefs.reset();
 }
 
 #pragma mark - LanguageSettingsCommands
diff --git a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
index 810977b..063e3d7 100644
--- a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
+++ b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
@@ -93,6 +93,10 @@
 
     pref_service_.registry()->RegisterStringPref(
         language::prefs::kAcceptLanguages, "en");
+    pref_service_.registry()->RegisterStringPref(
+        language::prefs::kSelectedLanguages, "");
+    pref_service_.registry()->RegisterListPref(
+        language::prefs::kForcedLanguages);
     pref_service_.registry()->RegisterListPref(
         language::prefs::kFluentLanguages,
         language::LanguagePrefs::GetDefaultFluentLanguages());
diff --git a/media/capture/video/fuchsia/video_capture_device_factory_fuchsia.cc b/media/capture/video/fuchsia/video_capture_device_factory_fuchsia.cc
index 538f01d2..1c42b27 100644
--- a/media/capture/video/fuchsia/video_capture_device_factory_fuchsia.cc
+++ b/media/capture/video/fuchsia/video_capture_device_factory_fuchsia.cc
@@ -208,8 +208,11 @@
       }
       if (it->second->is_pending()) {
         // If the device info request was still pending then consider it
-        // complete now.
-        OnDeviceInfoFetched();
+        // complete now. If this was the only device in pending state then all
+        // callbacks will be resolved in
+        // MaybeResolvePendingDeviceInfoCallbacks() called below.
+        DCHECK_GT(num_pending_device_info_requests_, 0U);
+        num_pending_device_info_requests_--;
       }
       devices_->erase(it);
       continue;
diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc
index 51989e0e..a1623bb 100644
--- a/media/capture/video/video_capture_device_unittest.cc
+++ b/media/capture/video/video_capture_device_unittest.cc
@@ -322,7 +322,8 @@
 
 #if defined(OS_WIN)
   bool UseWinMediaFoundation() {
-    return std::get<1>(GetParam()) == WIN_MEDIA_FOUNDATION;
+    return std::get<1>(GetParam()) == WIN_MEDIA_FOUNDATION &&
+           VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation();
   }
 #endif
 
diff --git a/media/capture/video/win/video_capture_device_factory_win.cc b/media/capture/video/win/video_capture_device_factory_win.cc
index ddfa7b0..43e0199 100644
--- a/media/capture/video/win/video_capture_device_factory_win.cc
+++ b/media/capture/video/win/video_capture_device_factory_win.cc
@@ -317,8 +317,9 @@
 // distributions such as Windows 7 N and Windows 7 KN.
 // static
 bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
-  static const bool g_dlls_available = LoadMediaFoundationDlls();
-  return g_dlls_available && InitializeMediaFoundation();
+  static const bool has_media_foundation =
+      LoadMediaFoundationDlls() && InitializeMediaFoundation();
+  return has_media_foundation;
 }
 
 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin()
diff --git a/media/gpu/vaapi/h264_encoder.cc b/media/gpu/vaapi/h264_encoder.cc
index c21c778..b17d1d23 100644
--- a/media/gpu/vaapi/h264_encoder.cc
+++ b/media/gpu/vaapi/h264_encoder.cc
@@ -18,12 +18,12 @@
 constexpr int kIPPeriod = 1;
 
 // The qp range is 0-51 in H264. Select 26 because of the center value.
-constexpr int kDefaultQP = 26;
+constexpr uint8_t kDefaultQP = 26;
 // Note: Webrtc default values are 24 and 37 respectively, see
 // h264_encoder_impl.cc.
 // These values are selected to make our VEA tests pass.
-constexpr int kMinQP = 24;
-constexpr int kMaxQP = 42;
+constexpr uint8_t kMinQP = 24;
+constexpr uint8_t kMaxQP = 42;
 
 // Subjectively chosen bitrate window size for rate control, in ms.
 constexpr int kCPBWindowSizeMs = 1500;
@@ -386,8 +386,9 @@
       curr_params_.max_ref_pic_list1_size > 0
           ? curr_params_.max_ref_pic_list1_size - 1
           : curr_params_.max_ref_pic_list1_size;
-  DCHECK_LE(curr_params_.initial_qp, 51);
-  current_pps_.pic_init_qp_minus26 = curr_params_.initial_qp - 26;
+  DCHECK_LE(curr_params_.initial_qp, 51u);
+  current_pps_.pic_init_qp_minus26 =
+      static_cast<int>(curr_params_.initial_qp) - 26;
   current_pps_.deblocking_filter_control_present_flag = true;
   current_pps_.transform_8x8_mode_flag =
       (current_sps_.profile_idc == H264SPS::kProfileIDCHigh);
diff --git a/media/gpu/vaapi/h264_encoder.h b/media/gpu/vaapi/h264_encoder.h
index 941860ca..27554232 100644
--- a/media/gpu/vaapi/h264_encoder.h
+++ b/media/gpu/vaapi/h264_encoder.h
@@ -54,9 +54,9 @@
     unsigned int cpb_size_bits;
 
     // Quantization parameter. Their ranges are 0-51.
-    int initial_qp;
-    int min_qp;
-    int max_qp;
+    uint8_t initial_qp;
+    uint8_t min_qp;
+    uint8_t max_qp;
 
     // Maxium Number of Reference frames.
     size_t max_num_ref_frames;
diff --git a/media/gpu/vaapi/test/vp9_decoder.cc b/media/gpu/vaapi/test/vp9_decoder.cc
index c7db541..9d6869c2 100644
--- a/media/gpu/vaapi/test/vp9_decoder.cc
+++ b/media/gpu/vaapi/test/vp9_decoder.cc
@@ -129,7 +129,13 @@
   }
 
   // [Re]create context for decode.
-  if (!va_context_ || va_context_->size() != size) {
+  // A resolution change may occur on a frame that is neither keyframe nor
+  // intra-only, i.e. may refer to earlier frames. If the earlier referred frame
+  // is larger than the new frame, consequently, do *not* recreate the context.
+  // See also
+  // https://cgit.freedesktop.org/gstreamer/gstreamer-vaapi/tree/gst-libs/gst/vaapi/gstvaapidecoder_vp9.c?h=1.18#n652
+  if (!va_context_ || va_context_->size().width() < size.width() ||
+      va_context_->size().height() < size.height()) {
     va_context_ =
         std::make_unique<ScopedVAContext>(va_device_, *va_config_, size);
   }
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 933214c..035361de 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -59,8 +59,32 @@
 // if encoder requests less.
 constexpr size_t kMinNumFramesInFlight = 4;
 
-// Percentage of bitrate set to be targeted by the HW encoder.
-constexpr unsigned int kTargetBitratePercentage = 90;
+void FillVAEncRateControlParams(
+    uint32_t bps,
+    uint32_t window_size,
+    uint32_t initial_qp,
+    uint32_t min_qp,
+    uint32_t max_qp,
+    uint32_t framerate,
+    uint32_t buffer_size,
+    VAEncMiscParameterRateControl& rate_control_param,
+    VAEncMiscParameterFrameRate& framerate_param,
+    VAEncMiscParameterHRD& hrd_param) {
+  memset(&rate_control_param, 0, sizeof(rate_control_param));
+  rate_control_param.bits_per_second = bps;
+  rate_control_param.window_size = window_size;
+  rate_control_param.initial_qp = initial_qp;
+  rate_control_param.min_qp = min_qp;
+  rate_control_param.max_qp = max_qp;
+  rate_control_param.rc_flags.bits.disable_frame_skip = true;
+
+  memset(&framerate_param, 0, sizeof(framerate_param));
+  framerate_param.framerate = framerate;
+
+  memset(&hrd_param, 0, sizeof(hrd_param));
+  hrd_param.buffer_size = buffer_size;
+  hrd_param.initial_buffer_fullness = buffer_size / 2;
+}
 
 // Calculate the size of the allocated buffer aligned to hardware/driver
 // requirements.
@@ -1178,21 +1202,18 @@
   ref_list_entry = slice_param.RefPicList1;
   std::for_each(ref_pic_list1.begin(), ref_pic_list1.end(), fill_ref_frame);
 
-  VAEncMiscParameterRateControl rate_control_param = {};
-  rate_control_param.bits_per_second = encode_params.bitrate_bps;
-  rate_control_param.target_percentage = kTargetBitratePercentage;
-  rate_control_param.window_size = encode_params.cpb_window_size_ms;
-  rate_control_param.initial_qp = pic_param.pic_init_qp;
-  rate_control_param.min_qp = encode_params.min_qp;
-  rate_control_param.max_qp = encode_params.max_qp;
-  rate_control_param.rc_flags.bits.disable_frame_skip = true;
-
-  VAEncMiscParameterFrameRate framerate_param = {};
-  framerate_param.framerate = encode_params.framerate;
-
-  VAEncMiscParameterHRD hrd_param = {};
-  hrd_param.buffer_size = encode_params.cpb_size_bits;
-  hrd_param.initial_buffer_fullness = hrd_param.buffer_size / 2;
+  VAEncMiscParameterRateControl rate_control_param;
+  VAEncMiscParameterFrameRate framerate_param;
+  VAEncMiscParameterHRD hrd_param;
+  FillVAEncRateControlParams(
+      encode_params.bitrate_bps,
+      base::strict_cast<uint32_t>(encode_params.cpb_window_size_ms),
+      base::strict_cast<uint32_t>(pic_param.pic_init_qp),
+      base::strict_cast<uint32_t>(encode_params.min_qp),
+      base::strict_cast<uint32_t>(encode_params.max_qp),
+      encode_params.framerate,
+      base::strict_cast<uint32_t>(encode_params.cpb_size_bits),
+      rate_control_param, framerate_param, hrd_param);
 
   job->AddSetupCallback(
       base::BindOnce(&VaapiVideoEncodeAccelerator::SubmitBuffer,
@@ -1389,22 +1410,19 @@
   qmatrix_buf.quantization_index_delta[4] =
       frame_header->quantization_hdr.uv_ac_delta;
 
-  VAEncMiscParameterRateControl rate_control_param = {};
-  rate_control_param.bits_per_second =
-      encode_params.bitrate_allocation.GetSumBps();
-  rate_control_param.target_percentage = kTargetBitratePercentage;
-  rate_control_param.window_size = encode_params.cpb_window_size_ms;
-  rate_control_param.initial_qp = encode_params.initial_qp;
-  rate_control_param.min_qp = encode_params.min_qp;
-  rate_control_param.max_qp = encode_params.max_qp;
-  rate_control_param.rc_flags.bits.disable_frame_skip = true;
-
-  VAEncMiscParameterFrameRate framerate_param = {};
-  framerate_param.framerate = encode_params.framerate;
-
-  VAEncMiscParameterHRD hrd_param = {};
-  hrd_param.buffer_size = encode_params.cpb_size_bits;
-  hrd_param.initial_buffer_fullness = hrd_param.buffer_size / 2;
+  VAEncMiscParameterRateControl rate_control_param;
+  VAEncMiscParameterFrameRate framerate_param;
+  VAEncMiscParameterHRD hrd_param;
+  FillVAEncRateControlParams(
+      base::checked_cast<uint32_t>(
+          encode_params.bitrate_allocation.GetSumBps()),
+      base::strict_cast<uint32_t>(encode_params.cpb_window_size_ms),
+      base::strict_cast<uint32_t>(encode_params.initial_qp),
+      base::strict_cast<uint32_t>(encode_params.min_qp),
+      base::strict_cast<uint32_t>(encode_params.max_qp),
+      encode_params.framerate,
+      base::strict_cast<uint32_t>(encode_params.cpb_size_bits),
+      rate_control_param, framerate_param, hrd_param);
 
   job->AddSetupCallback(
       base::BindOnce(&VaapiVideoEncodeAccelerator::SubmitBuffer,
@@ -1554,22 +1572,19 @@
     return true;
   }
 
-  VAEncMiscParameterRateControl rate_control_param = {};
-  rate_control_param.bits_per_second =
-      encode_params.bitrate_allocation.GetSumBps();
-  rate_control_param.target_percentage = kTargetBitratePercentage;
-  rate_control_param.window_size = encode_params.cpb_window_size_ms;
-  rate_control_param.initial_qp = encode_params.initial_qp;
-  rate_control_param.min_qp = encode_params.min_qp;
-  rate_control_param.max_qp = encode_params.max_qp;
-  rate_control_param.rc_flags.bits.disable_frame_skip = true;
-
-  VAEncMiscParameterFrameRate framerate_param = {};
-  framerate_param.framerate = encode_params.framerate;
-
-  VAEncMiscParameterHRD hrd_param = {};
-  hrd_param.buffer_size = encode_params.cpb_size_bits;
-  hrd_param.initial_buffer_fullness = hrd_param.buffer_size / 2;
+  VAEncMiscParameterRateControl rate_control_param;
+  VAEncMiscParameterFrameRate framerate_param;
+  VAEncMiscParameterHRD hrd_param;
+  FillVAEncRateControlParams(
+      base::checked_cast<uint32_t>(
+          encode_params.bitrate_allocation.GetSumBps()),
+      base::strict_cast<uint32_t>(encode_params.cpb_window_size_ms),
+      base::strict_cast<uint32_t>(encode_params.initial_qp),
+      base::strict_cast<uint32_t>(encode_params.min_qp),
+      base::strict_cast<uint32_t>(encode_params.max_qp),
+      encode_params.framerate,
+      base::strict_cast<uint32_t>(encode_params.cpb_size_bits),
+      rate_control_param, framerate_param, hrd_param);
 
   job->AddSetupCallback(base::BindOnce(
       &VaapiVideoEncodeAccelerator::SubmitVAEncMiscParamBuffer,
diff --git a/media/gpu/vaapi/vp8_encoder.cc b/media/gpu/vaapi/vp8_encoder.cc
index 2f63e42c..1d67d70 100644
--- a/media/gpu/vaapi/vp8_encoder.cc
+++ b/media/gpu/vaapi/vp8_encoder.cc
@@ -18,12 +18,12 @@
 
 // Quantization parameter. They are vp8 ac/dc indices and their ranges are
 // 0-127. Based on WebRTC's defaults.
-constexpr int kMinQP = 4;
+constexpr uint8_t kMinQP = 4;
 // b/110059922, crbug.com/1001900: Tuned 112->117 for bitrate issue in a lower
 // resolution (180p).
-constexpr int kMaxQP = 117;
+constexpr uint8_t kMaxQP = 117;
 // This stands for 32 as a real ac value (see rfc 14.1. table ac_qlookup).
-constexpr int kDefaultQP = 28;
+constexpr uint8_t kDefaultQP = 28;
 }  // namespace
 
 VP8Encoder::EncodeParams::EncodeParams()
diff --git a/media/gpu/vaapi/vp8_encoder.h b/media/gpu/vaapi/vp8_encoder.h
index 9f10ebef..9e6c952 100644
--- a/media/gpu/vaapi/vp8_encoder.h
+++ b/media/gpu/vaapi/vp8_encoder.h
@@ -40,9 +40,9 @@
 
     // Quantization parameter. They are vp8 ac/dc indices and their ranges are
     // 0-127.
-    int initial_qp;
-    int min_qp;
-    int max_qp;
+    uint8_t initial_qp;
+    uint8_t min_qp;
+    uint8_t max_qp;
 
     bool error_resilient_mode;
   };
diff --git a/media/gpu/vaapi/vp9_encoder.cc b/media/gpu/vaapi/vp9_encoder.cc
index e14f4fb..8e650d2 100644
--- a/media/gpu/vaapi/vp9_encoder.cc
+++ b/media/gpu/vaapi/vp9_encoder.cc
@@ -25,19 +25,19 @@
 
 // Quantization parameter. They are vp9 ac/dc indices and their ranges are
 // 0-255. Based on WebRTC's defaults.
-constexpr int kMinQP = 4;
-constexpr int kMaxQP = 112;
+constexpr uint8_t kMinQP = 4;
+constexpr uint8_t kMaxQP = 112;
 // The upper limitation of the quantization parameter for the software rate
 // controller. This is larger than |kMaxQP| because a driver might ignore the
 // specified maximum quantization parameter when the driver determines the
 // value, but it doesn't ignore the quantization parameter by the software rate
 // controller.
-constexpr int kMaxQPForSoftwareRateCtrl = 224;
+constexpr uint8_t kMaxQPForSoftwareRateCtrl = 224;
 
 // This stands for 31 as a real ac value (see rfc 8.6.1 table
 // ac_qlookup[3][256]). Note: This needs to be revisited once we have 10&12 bit
 // encoder support.
-constexpr int kDefaultQP = 24;
+constexpr uint8_t kDefaultQP = 24;
 
 // filter level may affect on quality at lower bitrates; for now,
 // we set a constant value (== 10) which is what other VA-API
@@ -47,8 +47,8 @@
 // Convert Qindex, whose range is 0-255, to the quantizer parameter used in
 // libvpx vp9 rate control, whose range is 0-63.
 // Cited from //third_party/libvpx/source/libvpx/vp9/encoder/vp9_quantize.cc.
-int QindexToQuantizer(int q_index) {
-  constexpr int kQuantizerToQindex[] = {
+uint8_t QindexToQuantizer(uint8_t q_index) {
+  constexpr uint8_t kQuantizerToQindex[] = {
       0,   4,   8,   12,  16,  20,  24,  28,  32,  36,  40,  44,  48,
       52,  56,  60,  64,  68,  72,  76,  80,  84,  88,  92,  96,  100,
       104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 148, 152,
diff --git a/media/gpu/vaapi/vp9_encoder.h b/media/gpu/vaapi/vp9_encoder.h
index 47cdabd..d58f386 100644
--- a/media/gpu/vaapi/vp9_encoder.h
+++ b/media/gpu/vaapi/vp9_encoder.h
@@ -44,9 +44,9 @@
 
     // Quantization parameter. They are vp9 ac/dc indices and their ranges are
     // 0-255.
-    int initial_qp;
-    int min_qp;
-    int max_qp;
+    uint8_t initial_qp;
+    uint8_t min_qp;
+    uint8_t max_qp;
 
     bool error_resilient_mode;
   };
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
index 31e5de74..b999624 100644
--- a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
+++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -970,7 +970,7 @@
     }
 
     private void assertOnThread() {
-        if (BuildConfig.DCHECK_IS_ON && !onThread()) {
+        if (BuildConfig.ENABLE_ASSERTS && !onThread()) {
             throw new IllegalStateException(
                     "Must be called on NetworkChangeNotifierAutoDetect thread.");
         }
diff --git a/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
index 70f1528..bc337323 100644
--- a/net/android/java/src/org/chromium/net/ProxyChangeListener.java
+++ b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -290,7 +290,7 @@
     }
 
     private void assertOnThread() {
-        if (BuildConfig.DCHECK_IS_ON && !onThread()) {
+        if (BuildConfig.ENABLE_ASSERTS && !onThread()) {
             throw new IllegalStateException("Must be called on ProxyChangeListener thread.");
         }
     }
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index ec797a60..b986097 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -26,7 +26,7 @@
     deps += [ ":remoting_host_branded" ]
   }
 
-  if (!is_chromeos_ash && !is_android && !is_ios) {
+  if (!is_chromeos_ash && !is_chromeos_lacros && !is_android && !is_ios) {
     deps += [
       "//remoting/host:remoting_native_messaging_host",
       "//remoting/host:remoting_native_messaging_manifests",
diff --git a/remoting/host/it2me/BUILD.gn b/remoting/host/it2me/BUILD.gn
index f252c618..1a82d84 100644
--- a/remoting/host/it2me/BUILD.gn
+++ b/remoting/host/it2me/BUILD.gn
@@ -72,20 +72,32 @@
   }
 }
 
-if (is_chromeos_ash) {
+if (is_chromeos_ash || is_chromeos_lacros) {
   source_set("chrome_os_host") {
     sources = [
-      "it2me_native_messaging_host_chromeos.cc",
-      "it2me_native_messaging_host_chromeos.h",
+      "it2me_native_messaging_host_allowed_origins.cc",
+      "it2me_native_messaging_host_allowed_origins.h",
     ]
 
-    deps = [
-      ":common",
-      "//skia",
-    ]
+    if (is_chromeos_lacros) {
+      sources += [
+        "it2me_native_messaging_host_lacros.cc",
+        "it2me_native_messaging_host_lacros.h",
+      ]
+    } else {
+      sources += [
+        "it2me_native_messaging_host_chromeos.cc",
+        "it2me_native_messaging_host_chromeos.h",
+      ]
 
-    if (use_ozone) {
-      deps += [ "//ui/ozone" ]
+      deps = [
+        ":common",
+        "//skia",
+      ]
+
+      if (use_ozone) {
+        deps += [ "//ui/ozone" ]
+      }
     }
   }
 }
diff --git a/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.cc b/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.cc
new file mode 100644
index 0000000..aa33552f8
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.cc
@@ -0,0 +1,23 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h"
+
+#include "base/stl_util.h"
+
+namespace remoting {
+
+// If you modify the list of allowed_origins, don't forget to update
+// remoting/host/it2me/com.google.chrome.remote_assistance.json.jinja2
+// to keep the two lists in sync.
+const char* const kIt2MeOrigins[] = {
+    "chrome-extension://inomeogfingihgjfjlpeplalcfajhgai/",
+    "chrome-extension://hpodccmdligbeohchckkeajbfohibipg/"};
+
+const size_t kIt2MeOriginsSize = base::size(kIt2MeOrigins);
+
+const char kIt2MeNativeMessageHostName[] =
+    "com.google.chrome.remote_assistance";
+
+}  // namespace remoting
diff --git a/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h b/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h
new file mode 100644
index 0000000..6d02878
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_allowed_origins.h
@@ -0,0 +1,22 @@
+// Copyright 2021 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 REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ALLOWED_ORIGINS_H_
+#define REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ALLOWED_ORIGINS_H_
+
+#include <stddef.h>
+
+namespace remoting {
+
+// The set of origins which are allowed to instantiate an It2Me host.
+extern const char* const kIt2MeOrigins[];
+
+// The number of entries defined in |kIt2MeOrigins|.
+extern const size_t kIt2MeOriginsSize;
+
+// The name used to register the It2Me native message host.
+extern const char kIt2MeNativeMessageHostName[];
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ALLOWED_ORIGINS_H_
diff --git a/remoting/host/it2me/it2me_native_messaging_host_chromeos.cc b/remoting/host/it2me/it2me_native_messaging_host_chromeos.cc
index 6f261b0..0316368 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_chromeos.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_chromeos.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/lazy_instance.h"
+#include "base/stl_util.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -37,4 +38,13 @@
   return host;
 }
 
+// If you modify the list of allowed_origins, don't forget to update
+// remoting/host/it2me/com.google.chrome.remote_assistance.json.jinja2
+// to keep the two lists in sync.
+const char* const kRemotingIt2MeOrigins[] = {
+    "chrome-extension://inomeogfingihgjfjlpeplalcfajhgai/",
+    "chrome-extension://hpodccmdligbeohchckkeajbfohibipg/"};
+
+const size_t kRemotingIt2MeOriginsCount = base::size(kRemotingIt2MeOrigins);
+
 }  // namespace remoting
diff --git a/remoting/host/it2me/it2me_native_messaging_host_chromeos.h b/remoting/host/it2me/it2me_native_messaging_host_chromeos.h
index c371e43..63b88cc 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_chromeos.h
+++ b/remoting/host/it2me/it2me_native_messaging_host_chromeos.h
@@ -22,13 +22,18 @@
 
 // Creates native messaging host on ChromeOS. Must be called on the UI thread
 // of the browser process.
-
 std::unique_ptr<extensions::NativeMessageHost>
 CreateIt2MeNativeMessagingHostForChromeOS(
     scoped_refptr<base::SingleThreadTaskRunner> io_runnner,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runnner,
     policy::PolicyService* policy_service);
 
+// The set of origins which are allowed to instantiate an It2Me host.
+extern const char* const kRemotingIt2MeOrigins[];
+
+// The number of entries defined in |kRemotingIt2MeOrigins|.
+extern const size_t kRemotingIt2MeOriginsCount;
+
 }  // namespace remoting
 
 #endif  // REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_CHROMEOS_H_
diff --git a/remoting/host/it2me/it2me_native_messaging_host_lacros.cc b/remoting/host/it2me/it2me_native_messaging_host_lacros.cc
new file mode 100644
index 0000000..a242356
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_lacros.cc
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/it2me/it2me_native_messaging_host_lacros.h"
+
+#include <memory>
+
+#include "base/notreached.h"
+
+namespace remoting {
+
+std::unique_ptr<extensions::NativeMessageHost>
+CreateIt2MeNativeMessagingHostForLacros(
+    scoped_refptr<base::SingleThreadTaskRunner> io_runnner,
+    scoped_refptr<base::SingleThreadTaskRunner> ui_runnner) {
+  // TODO(joedow): Implement a remote support host for LaCrOS.
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/it2me/it2me_native_messaging_host_lacros.h b/remoting/host/it2me/it2me_native_messaging_host_lacros.h
new file mode 100644
index 0000000..93feeeb
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_lacros.h
@@ -0,0 +1,28 @@
+// Copyright 2021 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 REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_LACROS_H_
+#define REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_LACROS_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "extensions/browser/api/messaging/native_message_host.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}  // namespace base
+
+namespace remoting {
+
+// Creates native messaging host on Lacros. Must be called on the UI thread of
+// the browser process.
+
+std::unique_ptr<extensions::NativeMessageHost>
+CreateIt2MeNativeMessagingHostForLacros(
+    scoped_refptr<base::SingleThreadTaskRunner> io_runnner,
+    scoped_refptr<base::SingleThreadTaskRunner> ui_runnner);
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_LACROS_H_
diff --git a/sandbox/linux/system_headers/arm_linux_syscalls.h b/sandbox/linux/system_headers/arm_linux_syscalls.h
index c39c22b..85da6f41 100644
--- a/sandbox/linux/system_headers/arm_linux_syscalls.h
+++ b/sandbox/linux/system_headers/arm_linux_syscalls.h
@@ -1441,12 +1441,168 @@
 #define __NR_io_pgetevents (__NR_SYSCALL_BASE+399)
 #endif
 
+#if !defined(__NR_migrate_pages)
+#define __NR_migrate_pages (__NR_SYSCALL_BASE + 400)
+#endif
+
+#if !defined(__NR_kexec_file_load)
+#define __NR_kexec_file_load (__NR_SYSCALL_BASE + 401)
+#endif
+
 #if !defined(__NR_clock_gettime64)
-#define __NR_clock_gettime64 (__NR_SYSCALL_BASE+403)
+#define __NR_clock_gettime64 (__NR_SYSCALL_BASE + 403)
+#endif
+
+#if !defined(__NR_clock_settime64)
+#define __NR_clock_settime64 (__NR_SYSCALL_BASE + 404)
+#endif
+
+#if !defined(__NR_clock_adjtime64)
+#define __NR_clock_adjtime64 (__NR_SYSCALL_BASE + 405)
+#endif
+
+#if !defined(__NR_clock_getres_time64)
+#define __NR_clock_getres_time64 (__NR_SYSCALL_BASE + 406)
 #endif
 
 #if !defined(__NR_clock_nanosleep_time64)
-#define __NR_clock_nanosleep_time64 (__NR_SYSCALL_BASE+407)
+#define __NR_clock_nanosleep_time64 (__NR_SYSCALL_BASE + 407)
+#endif
+
+#if !defined(__NR_timer_gettime64)
+#define __NR_timer_gettime64 (__NR_SYSCALL_BASE + 408)
+#endif
+
+#if !defined(__NR_timer_settime64)
+#define __NR_timer_settime64 (__NR_SYSCALL_BASE + 409)
+#endif
+
+#if !defined(__NR_timerfd_gettime64)
+#define __NR_timerfd_gettime64 (__NR_SYSCALL_BASE + 410)
+#endif
+
+#if !defined(__NR_timerfd_settime64)
+#define __NR_timerfd_settime64 (__NR_SYSCALL_BASE + 411)
+#endif
+
+#if !defined(__NR_utimensat_time64)
+#define __NR_utimensat_time64 (__NR_SYSCALL_BASE + 412)
+#endif
+
+#if !defined(__NR_pselect6_time64)
+#define __NR_pselect6_time64 (__NR_SYSCALL_BASE + 413)
+#endif
+
+#if !defined(__NR_ppoll_time64)
+#define __NR_ppoll_time64 (__NR_SYSCALL_BASE + 414)
+#endif
+
+#if !defined(__NR_io_pgetevents_time64)
+#define __NR_io_pgetevents_time64 (__NR_SYSCALL_BASE + 416)
+#endif
+
+#if !defined(__NR_recvmmsg_time64)
+#define __NR_recvmmsg_time64 (__NR_SYSCALL_BASE + 417)
+#endif
+
+#if !defined(__NR_mq_timedsend_time64)
+#define __NR_mq_timedsend_time64 (__NR_SYSCALL_BASE + 418)
+#endif
+
+#if !defined(__NR_mq_timedreceive_time64)
+#define __NR_mq_timedreceive_time64 (__NR_SYSCALL_BASE + 419)
+#endif
+
+#if !defined(__NR_semtimedop_time64)
+#define __NR_semtimedop_time64 (__NR_SYSCALL_BASE + 420)
+#endif
+
+#if !defined(__NR_rt_sigtimedwait_time64)
+#define __NR_rt_sigtimedwait_time64 (__NR_SYSCALL_BASE + 421)
+#endif
+
+#if !defined(__NR_futex_time64)
+#define __NR_futex_time64 (__NR_SYSCALL_BASE + 422)
+#endif
+
+#if !defined(__NR_sched_rr_get_interval_time64)
+#define __NR_sched_rr_get_interval_time64 (__NR_SYSCALL_BASE + 423)
+#endif
+
+#if !defined(__NR_pidfd_send_signal)
+#define __NR_pidfd_send_signal (__NR_SYSCALL_BASE + 424)
+#endif
+
+#if !defined(__NR_io_uring_setup)
+#define __NR_io_uring_setup (__NR_SYSCALL_BASE + 425)
+#endif
+
+#if !defined(__NR_io_uring_enter)
+#define __NR_io_uring_enter (__NR_SYSCALL_BASE + 426)
+#endif
+
+#if !defined(__NR_io_uring_register)
+#define __NR_io_uring_register (__NR_SYSCALL_BASE + 427)
+#endif
+
+#if !defined(__NR_open_tree)
+#define __NR_open_tree (__NR_SYSCALL_BASE + 428)
+#endif
+
+#if !defined(__NR_move_mount)
+#define __NR_move_mount (__NR_SYSCALL_BASE + 429)
+#endif
+
+#if !defined(__NR_fsopen)
+#define __NR_fsopen (__NR_SYSCALL_BASE + 430)
+#endif
+
+#if !defined(__NR_fsconfig)
+#define __NR_fsconfig (__NR_SYSCALL_BASE + 431)
+#endif
+
+#if !defined(__NR_fsmount)
+#define __NR_fsmount (__NR_SYSCALL_BASE + 432)
+#endif
+
+#if !defined(__NR_fspick)
+#define __NR_fspick (__NR_SYSCALL_BASE + 433)
+#endif
+
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open (__NR_SYSCALL_BASE + 434)
+#endif
+
+#if !defined(__NR_clone3)
+#define __NR_clone3 (__NR_SYSCALL_BASE + 435)
+#endif
+
+#if !defined(__NR_close_range)
+#define __NR_close_range (__NR_SYSCALL_BASE + 436)
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 (__NR_SYSCALL_BASE + 437)
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd (__NR_SYSCALL_BASE + 438)
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 (__NR_SYSCALL_BASE + 439)
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise (__NR_SYSCALL_BASE + 440)
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 (__NR_SYSCALL_BASE + 441)
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr (__NR_SYSCALL_BASE + 442)
 #endif
 
 // ARM private syscalls.
diff --git a/sandbox/linux/system_headers/mips_linux_syscalls.h b/sandbox/linux/system_headers/mips_linux_syscalls.h
index fa01b3b..50d9ea1 100644
--- a/sandbox/linux/system_headers/mips_linux_syscalls.h
+++ b/sandbox/linux/system_headers/mips_linux_syscalls.h
@@ -1433,12 +1433,256 @@
 #define __NR_memfd_create (__NR_Linux + 354)
 #endif
 
+#if !defined(__NR_bpf)
+#define __NR_bpf (__NR_Linux + 355)
+#endif
+
+#if !defined(__NR_execveat)
+#define __NR_execveat (__NR_Linux + 356)
+#endif
+
+#if !defined(__NR_userfaultfd)
+#define __NR_userfaultfd (__NR_Linux + 357)
+#endif
+
+#if !defined(__NR_membarrier)
+#define __NR_membarrier (__NR_Linux + 358)
+#endif
+
+#if !defined(__NR_mlock2)
+#define __NR_mlock2 (__NR_Linux + 359)
+#endif
+
+#if !defined(__NR_copy_file_range)
+#define __NR_copy_file_range (__NR_Linux + 360)
+#endif
+
+#if !defined(__NR_preadv2)
+#define __NR_preadv2 (__NR_Linux + 361)
+#endif
+
+#if !defined(__NR_pwritev2)
+#define __NR_pwritev2 (__NR_Linux + 362)
+#endif
+
+#if !defined(__NR_pkey_mprotect)
+#define __NR_pkey_mprotect (__NR_Linux + 363)
+#endif
+
+#if !defined(__NR_pkey_alloc)
+#define __NR_pkey_alloc (__NR_Linux + 364)
+#endif
+
+#if !defined(__NR_pkey_free)
+#define __NR_pkey_free (__NR_Linux + 365)
+#endif
+
+#if !defined(__NR_statx)
+#define __NR_statx (__NR_Linux + 366)
+#endif
+
+#if !defined(__NR_rseq)
+#define __NR_rseq (__NR_Linux + 367)
+#endif
+
+#if !defined(__NR_io_pgetevents)
+#define __NR_io_pgetevents (__NR_Linux + 368)
+#endif
+
+#if !defined(__NR_semget)
+#define __NR_semget (__NR_Linux + 393)
+#endif
+
+#if !defined(__NR_semctl)
+#define __NR_semctl (__NR_Linux + 394)
+#endif
+
+#if !defined(__NR_shmget)
+#define __NR_shmget (__NR_Linux + 395)
+#endif
+
+#if !defined(__NR_shmctl)
+#define __NR_shmctl (__NR_Linux + 396)
+#endif
+
+#if !defined(__NR_shmat)
+#define __NR_shmat (__NR_Linux + 397)
+#endif
+
+#if !defined(__NR_shmdt)
+#define __NR_shmdt (__NR_Linux + 398)
+#endif
+
+#if !defined(__NR_msgget)
+#define __NR_msgget (__NR_Linux + 399)
+#endif
+
+#if !defined(__NR_msgsnd)
+#define __NR_msgsnd (__NR_Linux + 400)
+#endif
+
+#if !defined(__NR_msgrcv)
+#define __NR_msgrcv (__NR_Linux + 401)
+#endif
+
+#if !defined(__NR_msgctl)
+#define __NR_msgctl (__NR_Linux + 402)
+#endif
+
 #if !defined(__NR_clock_gettime64)
 #define __NR_clock_gettime64 (__NR_Linux + 403)
 #endif
 
+#if !defined(__NR_clock_settime64)
+#define __NR_clock_settime64 (__NR_Linux + 404)
+#endif
+
+#if !defined(__NR_clock_adjtime64)
+#define __NR_clock_adjtime64 (__NR_Linux + 405)
+#endif
+
+#if !defined(__NR_clock_getres_time64)
+#define __NR_clock_getres_time64 (__NR_Linux + 406)
+#endif
+
 #if !defined(__NR_clock_nanosleep_time64)
 #define __NR_clock_nanosleep_time64 (__NR_Linux + 407)
 #endif
 
+#if !defined(__NR_timer_gettime64)
+#define __NR_timer_gettime64 (__NR_Linux + 408)
+#endif
+
+#if !defined(__NR_timer_settime64)
+#define __NR_timer_settime64 (__NR_Linux + 409)
+#endif
+
+#if !defined(__NR_timerfd_gettime64)
+#define __NR_timerfd_gettime64 (__NR_Linux + 410)
+#endif
+
+#if !defined(__NR_timerfd_settime64)
+#define __NR_timerfd_settime64 (__NR_Linux + 411)
+#endif
+
+#if !defined(__NR_utimensat_time64)
+#define __NR_utimensat_time64 (__NR_Linux + 412)
+#endif
+
+#if !defined(__NR_pselect6_time64)
+#define __NR_pselect6_time64 (__NR_Linux + 413)
+#endif
+
+#if !defined(__NR_ppoll_time64)
+#define __NR_ppoll_time64 (__NR_Linux + 414)
+#endif
+
+#if !defined(__NR_io_pgetevents_time64)
+#define __NR_io_pgetevents_time64 (__NR_Linux + 416)
+#endif
+
+#if !defined(__NR_recvmmsg_time64)
+#define __NR_recvmmsg_time64 (__NR_Linux + 417)
+#endif
+
+#if !defined(__NR_mq_timedsend_time64)
+#define __NR_mq_timedsend_time64 (__NR_Linux + 418)
+#endif
+
+#if !defined(__NR_mq_timedreceive_time64)
+#define __NR_mq_timedreceive_time64 (__NR_Linux + 419)
+#endif
+
+#if !defined(__NR_semtimedop_time64)
+#define __NR_semtimedop_time64 (__NR_Linux + 420)
+#endif
+
+#if !defined(__NR_rt_sigtimedwait_time64)
+#define __NR_rt_sigtimedwait_time64 (__NR_Linux + 421)
+#endif
+
+#if !defined(__NR_futex_time64)
+#define __NR_futex_time64 (__NR_Linux + 422)
+#endif
+
+#if !defined(__NR_sched_rr_get_interval_time64)
+#define __NR_sched_rr_get_interval_time64 (__NR_Linux + 423)
+#endif
+
+#if !defined(__NR_pidfd_send_signal)
+#define __NR_pidfd_send_signal (__NR_Linux + 424)
+#endif
+
+#if !defined(__NR_io_uring_setup)
+#define __NR_io_uring_setup (__NR_Linux + 425)
+#endif
+
+#if !defined(__NR_io_uring_enter)
+#define __NR_io_uring_enter (__NR_Linux + 426)
+#endif
+
+#if !defined(__NR_io_uring_register)
+#define __NR_io_uring_register (__NR_Linux + 427)
+#endif
+
+#if !defined(__NR_open_tree)
+#define __NR_open_tree (__NR_Linux + 428)
+#endif
+
+#if !defined(__NR_move_mount)
+#define __NR_move_mount (__NR_Linux + 429)
+#endif
+
+#if !defined(__NR_fsopen)
+#define __NR_fsopen (__NR_Linux + 430)
+#endif
+
+#if !defined(__NR_fsconfig)
+#define __NR_fsconfig (__NR_Linux + 431)
+#endif
+
+#if !defined(__NR_fsmount)
+#define __NR_fsmount (__NR_Linux + 432)
+#endif
+
+#if !defined(__NR_fspick)
+#define __NR_fspick (__NR_Linux + 433)
+#endif
+
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open (__NR_Linux + 434)
+#endif
+
+#if !defined(__NR_clone3)
+#define __NR_clone3 (__NR_Linux + 435)
+#endif
+
+#if !defined(__NR_close_range)
+#define __NR_close_range (__NR_Linux + 436)
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 (__NR_Linux + 437)
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd (__NR_Linux + 438)
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 (__NR_Linux + 439)
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise (__NR_Linux + 440)
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 (__NR_Linux + 441)
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr (__NR_Linux + 442)
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_SYSCALLS_H_
diff --git a/sandbox/linux/system_headers/x86_32_linux_syscalls.h b/sandbox/linux/system_headers/x86_32_linux_syscalls.h
index 7613c9b..1720edb 100644
--- a/sandbox/linux/system_headers/x86_32_linux_syscalls.h
+++ b/sandbox/linux/system_headers/x86_32_linux_syscalls.h
@@ -1710,5 +1710,33 @@
 #define __NR_clone3 435
 #endif
 
+#if !defined(__NR_close_range)
+#define __NR_close_range 436
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 437
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd 438
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 439
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise 440
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 441
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr 442
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_
 
diff --git a/services/video_capture/BUILD.gn b/services/video_capture/BUILD.gn
index ba597d2..ef795f2f 100644
--- a/services/video_capture/BUILD.gn
+++ b/services/video_capture/BUILD.gn
@@ -58,9 +58,26 @@
     "//media/capture:capture_switches",
   ]
 
+  if (is_chromeos) {
+    deps += [ "//chromeos/crosapi/mojom" ]
+  }
+
   if (is_chromeos_ash) {
     public_deps += [ "//media/capture/video/chromeos/mojom:cros_camera" ]
   }
+
+  if (is_chromeos_lacros) {
+    sources += [
+      "lacros/device_factory_adapter_lacros.cc",
+      "lacros/device_factory_adapter_lacros.h",
+      "lacros/device_proxy_lacros.cc",
+      "lacros/device_proxy_lacros.h",
+      "lacros/video_frame_handler_proxy_lacros.cc",
+      "lacros/video_frame_handler_proxy_lacros.h",
+    ]
+
+    deps += [ "//chromeos/lacros" ]
+  }
 }
 
 source_set("tests") {
diff --git a/services/video_capture/DEPS b/services/video_capture/DEPS
index 16163ad..250a48f 100644
--- a/services/video_capture/DEPS
+++ b/services/video_capture/DEPS
@@ -1,8 +1,10 @@
 include_rules = [
+  "+chromeos/crosapi",
+  "+chromeos/lacros",
   "+gpu/command_buffer/client",
   "+media/base",
   "+media/mojo",
   "+media/capture",
-  "+ui/gfx/geometry",
+  "+ui/gfx",
   "+services/viz/public/cpp/gpu",
 ]
diff --git a/services/video_capture/lacros/OWNERS b/services/video_capture/lacros/OWNERS
new file mode 100644
index 0000000..6f44092
--- /dev/null
+++ b/services/video_capture/lacros/OWNERS
@@ -0,0 +1,2 @@
+wtlee@chromium.org
+shik@chromium.org
diff --git a/services/video_capture/lacros/device_factory_adapter_lacros.cc b/services/video_capture/lacros/device_factory_adapter_lacros.cc
new file mode 100644
index 0000000..cbd3ab5
--- /dev/null
+++ b/services/video_capture/lacros/device_factory_adapter_lacros.cc
@@ -0,0 +1,114 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/video_capture/lacros/device_factory_adapter_lacros.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/notreached.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/video_capture/lacros/device_proxy_lacros.h"
+#include "services/video_capture/public/uma/video_capture_service_event.h"
+
+namespace video_capture {
+
+DeviceFactoryAdapterLacros::DeviceFactoryAdapterLacros(
+    mojo::PendingRemote<crosapi::mojom::VideoCaptureDeviceFactory>
+        device_factory_ash)
+    : device_factory_ash_(std::move(device_factory_ash)) {}
+
+DeviceFactoryAdapterLacros::~DeviceFactoryAdapterLacros() = default;
+
+void DeviceFactoryAdapterLacros::GetDeviceInfos(
+    GetDeviceInfosCallback callback) {
+  DCHECK(device_factory_ash_.is_bound());
+  device_factory_ash_->GetDeviceInfos(std::move(callback));
+}
+
+void DeviceFactoryAdapterLacros::CreateDevice(
+    const std::string& device_id,
+    mojo::PendingReceiver<mojom::Device> device_receiver,
+    CreateDeviceCallback callback) {
+  DCHECK(device_factory_ash_.is_bound());
+  mojo::PendingRemote<crosapi::mojom::VideoCaptureDevice> proxy_remote;
+  auto proxy_receiver = proxy_remote.InitWithNewPipeAndPassReceiver();
+  // Since |device_proxy| is owned by this instance and the cleanup callback is
+  // only called within the lifetime of |device_proxy|, it should be safe to use
+  // base::Unretained(this) here.
+  auto device_proxy = std::make_unique<DeviceProxyLacros>(
+      std::move(device_receiver), std::move(proxy_remote),
+      base::BindOnce(
+          &DeviceFactoryAdapterLacros::OnClientConnectionErrorOrClose,
+          base::Unretained(this), device_id));
+
+  auto wrapped_callback = base::BindOnce(
+      [](CreateDeviceCallback callback,
+         crosapi::mojom::DeviceAccessResultCode code) {
+        video_capture::mojom::DeviceAccessResultCode video_capture_result_code;
+        switch (code) {
+          case crosapi::mojom::DeviceAccessResultCode::NOT_INITIALIZED:
+            video_capture_result_code =
+                video_capture::mojom::DeviceAccessResultCode::NOT_INITIALIZED;
+            break;
+          case crosapi::mojom::DeviceAccessResultCode::SUCCESS:
+            video_capture_result_code =
+                video_capture::mojom::DeviceAccessResultCode::SUCCESS;
+            break;
+          case crosapi::mojom::DeviceAccessResultCode::ERROR_DEVICE_NOT_FOUND:
+            video_capture_result_code = video_capture::mojom::
+                DeviceAccessResultCode::ERROR_DEVICE_NOT_FOUND;
+            break;
+          default:
+            NOTREACHED() << "Unexpected device access result code";
+        }
+        std::move(callback).Run(video_capture_result_code);
+      },
+      std::move(callback));
+
+  devices_.emplace(device_id, std::move(device_proxy));
+  device_factory_ash_->CreateDevice(device_id, std::move(proxy_receiver),
+                                    std::move(wrapped_callback));
+}
+
+void DeviceFactoryAdapterLacros::AddSharedMemoryVirtualDevice(
+    const media::VideoCaptureDeviceInfo& device_info,
+    mojo::PendingRemote<mojom::Producer> producer,
+    bool send_buffer_handles_to_producer_as_raw_file_descriptors,
+    mojo::PendingReceiver<mojom::SharedMemoryVirtualDevice>
+        virtual_device_receiver) {
+  NOTREACHED();
+}
+
+void DeviceFactoryAdapterLacros::AddTextureVirtualDevice(
+    const media::VideoCaptureDeviceInfo& device_info,
+    mojo::PendingReceiver<mojom::TextureVirtualDevice>
+        virtual_device_receiver) {
+  NOTREACHED();
+}
+
+void DeviceFactoryAdapterLacros::AddGpuMemoryBufferVirtualDevice(
+    const media::VideoCaptureDeviceInfo& device_info,
+    mojo::PendingReceiver<mojom::GpuMemoryBufferVirtualDevice>
+        virtual_device_receiver) {
+  NOTREACHED();
+}
+
+void DeviceFactoryAdapterLacros::RegisterVirtualDevicesChangedObserver(
+    mojo::PendingRemote<mojom::DevicesChangedObserver> observer,
+    bool raise_event_if_virtual_devices_already_present) {
+  NOTREACHED();
+}
+
+void DeviceFactoryAdapterLacros::OnClientConnectionErrorOrClose(
+    std::string device_id) {
+  video_capture::uma::LogVideoCaptureServiceEvent(
+      video_capture::uma::SERVICE_LOST_CONNECTION_TO_BROWSER);
+
+  devices_.erase(device_id);
+}
+
+}  // namespace video_capture
diff --git a/services/video_capture/lacros/device_factory_adapter_lacros.h b/services/video_capture/lacros/device_factory_adapter_lacros.h
new file mode 100644
index 0000000..08789c4
--- /dev/null
+++ b/services/video_capture/lacros/device_factory_adapter_lacros.h
@@ -0,0 +1,69 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_FACTORY_ADAPTER_LACROS_H_
+#define SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_FACTORY_ADAPTER_LACROS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/device_factory.h"
+#include "services/video_capture/public/mojom/device_factory.mojom.h"
+
+namespace video_capture {
+
+class DeviceProxyLacros;
+
+// A proxy which forwards the requests to the actual
+// video_capture::DeviceFactory in Ash-Chrome.
+class DeviceFactoryAdapterLacros : public DeviceFactory {
+ public:
+  explicit DeviceFactoryAdapterLacros(
+      mojo::PendingRemote<crosapi::mojom::VideoCaptureDeviceFactory>
+          device_factory_ash);
+  DeviceFactoryAdapterLacros(const DeviceFactoryAdapterLacros&) = delete;
+  DeviceFactoryAdapterLacros& operator=(const DeviceFactoryAdapterLacros&) =
+      delete;
+  ~DeviceFactoryAdapterLacros() override;
+
+ private:
+  // DeviceFactory implementation.
+  void GetDeviceInfos(GetDeviceInfosCallback callback) override;
+  void CreateDevice(const std::string& device_id,
+                    mojo::PendingReceiver<mojom::Device> device_receiver,
+                    CreateDeviceCallback callback) override;
+  void AddSharedMemoryVirtualDevice(
+      const media::VideoCaptureDeviceInfo& device_info,
+      mojo::PendingRemote<mojom::Producer> producer,
+      bool send_buffer_handles_to_producer_as_raw_file_descriptors,
+      mojo::PendingReceiver<mojom::SharedMemoryVirtualDevice>
+          virtual_device_receiver) override;
+  void AddTextureVirtualDevice(
+      const media::VideoCaptureDeviceInfo& device_info,
+      mojo::PendingReceiver<mojom::TextureVirtualDevice>
+          virtual_device_receiver) override;
+  void AddGpuMemoryBufferVirtualDevice(
+      const media::VideoCaptureDeviceInfo& device_info,
+      mojo::PendingReceiver<mojom::GpuMemoryBufferVirtualDevice>
+          virtual_device_receiver) override;
+  void RegisterVirtualDevicesChangedObserver(
+      mojo::PendingRemote<mojom::DevicesChangedObserver> observer,
+      bool raise_event_if_virtual_devices_already_present) override;
+
+  void OnClientConnectionErrorOrClose(std::string device_id);
+
+  mojo::Remote<crosapi::mojom::VideoCaptureDeviceFactory> device_factory_ash_;
+
+  // The key is the device id used in blink::MediaStreamDevice.
+  base::flat_map<std::string, std::unique_ptr<DeviceProxyLacros>> devices_;
+};
+
+}  // namespace video_capture
+
+#endif  // SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_FACTORY_ADAPTER_LACROS_H_
diff --git a/services/video_capture/lacros/device_proxy_lacros.cc b/services/video_capture/lacros/device_proxy_lacros.cc
new file mode 100644
index 0000000..59740da0
--- /dev/null
+++ b/services/video_capture/lacros/device_proxy_lacros.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/video_capture/lacros/device_proxy_lacros.h"
+
+#include <memory>
+#include <utility>
+
+#include "services/video_capture/lacros/video_frame_handler_proxy_lacros.h"
+
+namespace video_capture {
+
+DeviceProxyLacros::DeviceProxyLacros(
+    mojo::PendingReceiver<mojom::Device> device_receiver,
+    mojo::PendingRemote<crosapi::mojom::VideoCaptureDevice> proxy_remote,
+    base::OnceClosure cleanup_callback)
+    : device_(std::move(proxy_remote)) {
+  receiver_.Bind(std::move(device_receiver));
+  receiver_.set_disconnect_handler(std::move(cleanup_callback));
+}
+
+DeviceProxyLacros::~DeviceProxyLacros() = default;
+
+void DeviceProxyLacros::Start(
+    const media::VideoCaptureParams& requested_settings,
+    mojo::PendingRemote<mojom::VideoFrameHandler> handler) {
+  mojo::PendingRemote<crosapi::mojom::VideoFrameHandler> proxy_handler_remote;
+  handler_ = std::make_unique<VideoFrameHandlerProxyLacros>(
+      proxy_handler_remote.InitWithNewPipeAndPassReceiver(),
+      std::move(handler));
+  device_->Start(std::move(requested_settings),
+                 std::move(proxy_handler_remote));
+}
+
+void DeviceProxyLacros::MaybeSuspend() {
+  device_->MaybeSuspend();
+}
+
+void DeviceProxyLacros::Resume() {
+  device_->Resume();
+}
+
+void DeviceProxyLacros::GetPhotoState(GetPhotoStateCallback callback) {
+  device_->GetPhotoState(std::move(callback));
+}
+
+void DeviceProxyLacros::SetPhotoOptions(media::mojom::PhotoSettingsPtr settings,
+                                        SetPhotoOptionsCallback callback) {
+  device_->SetPhotoOptions(std::move(settings), std::move(callback));
+}
+
+void DeviceProxyLacros::TakePhoto(TakePhotoCallback callback) {
+  device_->TakePhoto(std::move(callback));
+}
+
+void DeviceProxyLacros::ProcessFeedback(
+    const media::VideoFrameFeedback& feedback) {
+  device_->ProcessFeedback(std::move(feedback));
+}
+
+}  // namespace video_capture
diff --git a/services/video_capture/lacros/device_proxy_lacros.h b/services/video_capture/lacros/device_proxy_lacros.h
new file mode 100644
index 0000000..b7ce0e5
--- /dev/null
+++ b/services/video_capture/lacros/device_proxy_lacros.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_PROXY_LACROS_H_
+#define SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_PROXY_LACROS_H_
+
+#include <memory>
+
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
+
+namespace video_capture {
+
+class VideoFrameHandlerProxyLacros;
+
+// A proxy which is used for communication between the client on Lacros-Chrome
+// and the actual video_capture::Device in Ash-Chrome.
+class DeviceProxyLacros : public mojom::Device {
+ public:
+  DeviceProxyLacros(
+      mojo::PendingReceiver<mojom::Device> device_receiver,
+      mojo::PendingRemote<crosapi::mojom::VideoCaptureDevice> proxy_remote,
+      base::OnceClosure cleanup_callback);
+  DeviceProxyLacros(const DeviceProxyLacros&) = delete;
+  DeviceProxyLacros& operator=(const DeviceProxyLacros&) = delete;
+  ~DeviceProxyLacros() override;
+
+ private:
+  // mojom::Device implementation.
+  void Start(const media::VideoCaptureParams& requested_settings,
+             mojo::PendingRemote<mojom::VideoFrameHandler> handler) override;
+  void MaybeSuspend() override;
+  void Resume() override;
+  void GetPhotoState(GetPhotoStateCallback callback) override;
+  void SetPhotoOptions(media::mojom::PhotoSettingsPtr settings,
+                       SetPhotoOptionsCallback callback) override;
+  void TakePhoto(TakePhotoCallback callback) override;
+  void ProcessFeedback(const media::VideoFrameFeedback& feedback) override;
+
+  std::unique_ptr<VideoFrameHandlerProxyLacros> handler_;
+
+  mojo::Receiver<mojom::Device> receiver_{this};
+
+  mojo::Remote<crosapi::mojom::VideoCaptureDevice> device_;
+};
+
+}  // namespace video_capture
+
+#endif  // SERVICES_VIDEO_CAPTURE_LACROS_DEVICE_PROXY_LACROS_H_
diff --git a/services/video_capture/lacros/video_frame_handler_proxy_lacros.cc b/services/video_capture/lacros/video_frame_handler_proxy_lacros.cc
new file mode 100644
index 0000000..10b2a91e
--- /dev/null
+++ b/services/video_capture/lacros/video_frame_handler_proxy_lacros.cc
@@ -0,0 +1,172 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/video_capture/lacros/video_frame_handler_proxy_lacros.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/notreached.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "ui/gfx/gpu_memory_buffer.h"
+#include "ui/gfx/mojom/buffer_types.mojom.h"
+#include "ui/gfx/mojom/native_handle_types.mojom.h"
+
+namespace video_capture {
+
+namespace {
+
+mojom::ReadyFrameInBufferPtr ToVideoCaptureBuffer(
+    crosapi::mojom::ReadyFrameInBufferPtr buffer) {
+  auto video_capture_buffer = mojom::ReadyFrameInBuffer::New();
+  video_capture_buffer->buffer_id = buffer->buffer_id;
+  video_capture_buffer->frame_feedback_id = buffer->frame_feedback_id;
+
+  mojo::PendingRemote<mojom::ScopedAccessPermission> access_permission;
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<VideoFrameHandlerProxyLacros::AccessPermissionProxy>(
+          std::move(buffer->access_permission)),
+      access_permission.InitWithNewPipeAndPassReceiver());
+  video_capture_buffer->access_permission = std::move(access_permission);
+
+  const auto& buffer_info = buffer->frame_info;
+  auto video_capture_buffer_info = media::mojom::VideoFrameInfo::New();
+  video_capture_buffer_info->timestamp = buffer_info->timestamp;
+  video_capture_buffer_info->pixel_format = buffer_info->pixel_format;
+  video_capture_buffer_info->coded_size = buffer_info->coded_size;
+  video_capture_buffer_info->visible_rect = buffer_info->visible_rect;
+
+  media::VideoFrameMetadata media_frame_metadata;
+  switch (buffer_info->rotation) {
+    case crosapi::mojom::VideoRotation::kVideoRotation0:
+      media_frame_metadata.transformation =
+          media::VideoTransformation(media::VideoRotation::VIDEO_ROTATION_0);
+      break;
+    case crosapi::mojom::VideoRotation::kVideoRotation90:
+      media_frame_metadata.transformation =
+          media::VideoTransformation(media::VideoRotation::VIDEO_ROTATION_90);
+      break;
+    case crosapi::mojom::VideoRotation::kVideoRotation180:
+      media_frame_metadata.transformation =
+          media::VideoTransformation(media::VideoRotation::VIDEO_ROTATION_180);
+      break;
+    case crosapi::mojom::VideoRotation::kVideoRotation270:
+      media_frame_metadata.transformation =
+          media::VideoTransformation(media::VideoRotation::VIDEO_ROTATION_270);
+      break;
+    default:
+      NOTREACHED() << "Unexpected rotation in video frame metadata";
+  }
+  media_frame_metadata.reference_time = buffer_info->reference_time;
+
+  video_capture_buffer_info->metadata = std::move(media_frame_metadata);
+  video_capture_buffer->frame_info = std::move(video_capture_buffer_info);
+
+  return video_capture_buffer;
+}
+
+gfx::GpuMemoryBufferHandle ToGfxGpuMemoryBufferHandle(
+    crosapi::mojom::GpuMemoryBufferHandlePtr buffer_handle) {
+  gfx::GpuMemoryBufferHandle gfx_buffer_handle;
+  gfx_buffer_handle.id = gfx::GpuMemoryBufferId(buffer_handle->id);
+  gfx_buffer_handle.offset = buffer_handle->offset;
+  gfx_buffer_handle.stride = buffer_handle->stride;
+
+  if (buffer_handle->platform_handle) {
+    auto& platform_handle = buffer_handle->platform_handle;
+    if (platform_handle->is_shared_memory_handle()) {
+      gfx_buffer_handle.region =
+          std::move(platform_handle->get_shared_memory_handle());
+    } else if (platform_handle->is_native_pixmap_handle()) {
+      auto& native_pixmap_handle = platform_handle->get_native_pixmap_handle();
+      gfx::NativePixmapHandle gfx_native_pixmap_handle;
+      gfx_native_pixmap_handle.planes = std::move(native_pixmap_handle->planes);
+      gfx_native_pixmap_handle.modifier = native_pixmap_handle->modifier;
+      gfx_buffer_handle.native_pixmap_handle =
+          std::move(gfx_native_pixmap_handle);
+    }
+  }
+  return gfx_buffer_handle;
+}
+
+}  // namespace
+
+VideoFrameHandlerProxyLacros::VideoFrameHandlerProxyLacros(
+    mojo::PendingReceiver<crosapi::mojom::VideoFrameHandler> proxy_receiver,
+    mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote)
+    : handler_(std::move(handler_remote)) {
+  receiver_.Bind(std::move(proxy_receiver));
+}
+
+VideoFrameHandlerProxyLacros::~VideoFrameHandlerProxyLacros() = default;
+
+VideoFrameHandlerProxyLacros::AccessPermissionProxy::AccessPermissionProxy(
+    mojo::PendingRemote<crosapi::mojom::ScopedAccessPermission> remote)
+    : remote_(std::move(remote)) {}
+
+VideoFrameHandlerProxyLacros::AccessPermissionProxy::~AccessPermissionProxy() =
+    default;
+
+void VideoFrameHandlerProxyLacros::OnNewBuffer(
+    int buffer_id,
+    crosapi::mojom::VideoBufferHandlePtr buffer_handle) {
+  media::mojom::VideoBufferHandlePtr media_handle =
+      media::mojom::VideoBufferHandle::New();
+
+  if (buffer_handle->is_shared_buffer_handle()) {
+    media_handle->set_shared_buffer_handle(
+        buffer_handle->get_shared_buffer_handle()->Clone(
+            mojo::SharedBufferHandle::AccessMode::READ_WRITE));
+  } else if (buffer_handle->is_gpu_memory_buffer_handle()) {
+    media_handle->set_gpu_memory_buffer_handle(ToGfxGpuMemoryBufferHandle(
+        std::move(buffer_handle->get_gpu_memory_buffer_handle())));
+  } else {
+    NOTREACHED() << "Unexpected new buffer type";
+  }
+  handler_->OnNewBuffer(buffer_id, std::move(media_handle));
+}
+
+void VideoFrameHandlerProxyLacros::OnFrameReadyInBuffer(
+    crosapi::mojom::ReadyFrameInBufferPtr buffer,
+    std::vector<crosapi::mojom::ReadyFrameInBufferPtr> scaled_buffers) {
+  mojom::ReadyFrameInBufferPtr video_capture_buffer =
+      ToVideoCaptureBuffer(std::move(buffer));
+  std::vector<mojom::ReadyFrameInBufferPtr> video_capture_scaled_buffers;
+  for (auto& b : scaled_buffers)
+    video_capture_scaled_buffers.push_back(ToVideoCaptureBuffer(std::move(b)));
+
+  handler_->OnFrameReadyInBuffer(std::move(video_capture_buffer),
+                                 std::move(video_capture_scaled_buffers));
+}
+
+void VideoFrameHandlerProxyLacros::OnBufferRetired(int buffer_id) {
+  handler_->OnBufferRetired(buffer_id);
+}
+
+void VideoFrameHandlerProxyLacros::OnError(media::VideoCaptureError error) {
+  handler_->OnError(error);
+}
+
+void VideoFrameHandlerProxyLacros::OnFrameDropped(
+    media::VideoCaptureFrameDropReason reason) {
+  handler_->OnFrameDropped(reason);
+}
+
+void VideoFrameHandlerProxyLacros::OnLog(const std::string& message) {
+  handler_->OnLog(message);
+}
+
+void VideoFrameHandlerProxyLacros::OnStarted() {
+  handler_->OnStarted();
+}
+
+void VideoFrameHandlerProxyLacros::OnStartedUsingGpuDecode() {
+  handler_->OnStartedUsingGpuDecode();
+}
+
+void VideoFrameHandlerProxyLacros::OnStopped() {
+  handler_->OnStopped();
+}
+
+}  // namespace video_capture
diff --git a/services/video_capture/lacros/video_frame_handler_proxy_lacros.h b/services/video_capture/lacros/video_frame_handler_proxy_lacros.h
new file mode 100644
index 0000000..3cc1e2e
--- /dev/null
+++ b/services/video_capture/lacros/video_frame_handler_proxy_lacros.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIDEO_CAPTURE_LACROS_VIDEO_FRAME_HANDLER_PROXY_LACROS_H_
+#define SERVICES_VIDEO_CAPTURE_LACROS_VIDEO_FRAME_HANDLER_PROXY_LACROS_H_
+
+#include <string>
+
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
+
+namespace video_capture {
+
+// A proxy which is used for communication between the actual handler in
+// Lacros-Chrome and the video_capture::Device in Ash-Chrome. Since we
+// have simplified some structures in crosapi video capture interface to reduce
+// dependencies to other components, this class should also be responsible for
+// translating those structures between the interfaces.
+class VideoFrameHandlerProxyLacros : public crosapi::mojom::VideoFrameHandler {
+ public:
+  VideoFrameHandlerProxyLacros(
+      mojo::PendingReceiver<crosapi::mojom::VideoFrameHandler> proxy_receiver,
+      mojo::PendingRemote<mojom::VideoFrameHandler> handler_remote);
+  VideoFrameHandlerProxyLacros(const VideoFrameHandlerProxyLacros&) = delete;
+  VideoFrameHandlerProxyLacros& operator=(const VideoFrameHandlerProxyLacros&) =
+      delete;
+  ~VideoFrameHandlerProxyLacros() override;
+
+  class AccessPermissionProxy : public mojom::ScopedAccessPermission {
+   public:
+    AccessPermissionProxy(
+        mojo::PendingRemote<crosapi::mojom::ScopedAccessPermission> remote);
+    AccessPermissionProxy(const AccessPermissionProxy&) = delete;
+    AccessPermissionProxy& operator=(const AccessPermissionProxy&) = delete;
+    ~AccessPermissionProxy() override;
+
+   private:
+    mojo::Remote<crosapi::mojom::ScopedAccessPermission> remote_;
+  };
+
+ private:
+  // crosapi::mojom::VideoFrameHandler implementation.
+  void OnNewBuffer(int buffer_id,
+                   crosapi::mojom::VideoBufferHandlePtr buffer_handle) override;
+  void OnFrameReadyInBuffer(crosapi::mojom::ReadyFrameInBufferPtr buffer,
+                            std::vector<crosapi::mojom::ReadyFrameInBufferPtr>
+                                scaled_buffers) override;
+  void OnBufferRetired(int buffer_id) override;
+  void OnError(media::VideoCaptureError error) override;
+  void OnFrameDropped(media::VideoCaptureFrameDropReason reason) override;
+  void OnLog(const std::string& message) override;
+  void OnStarted() override;
+  void OnStartedUsingGpuDecode() override;
+  void OnStopped() override;
+
+  mojo::Receiver<crosapi::mojom::VideoFrameHandler> receiver_{this};
+
+  mojo::Remote<mojom::VideoFrameHandler> handler_;
+};
+
+}  // namespace video_capture
+
+#endif  // SERVICES_VIDEO_CAPTURE_LACROS_VIDEO_FRAME_HANDLER_PROXY_LACROS_H_
diff --git a/services/video_capture/video_capture_service_impl.cc b/services/video_capture/video_capture_service_impl.cc
index 1b82ce7..f7350a9 100644
--- a/services/video_capture/video_capture_service_impl.cc
+++ b/services/video_capture/video_capture_service_impl.cc
@@ -36,6 +36,12 @@
 #include "media/capture/video/chromeos/camera_app_device_bridge_impl.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/crosapi/mojom/video_capture.mojom.h"
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+#include "services/video_capture/lacros/device_factory_adapter_lacros.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
 namespace video_capture {
 
 // Intended usage of this class is to instantiate on any sequence, and then
@@ -184,6 +190,26 @@
               &GpuDependenciesContext::CreateJpegDecodeAccelerator,
               gpu_dependencies_context_->GetWeakPtr()),
           gpu_dependencies_context_->GetTaskRunner()));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
+  DCHECK(lacros_chrome_service) << "Failed to get Lacros Chrome Service";
+
+  if (!lacros_chrome_service->IsVideoCaptureDeviceFactoryAvailable()) {
+    LOG(WARNING)
+        << "Connected to an older version of ash. Use device factory in "
+           "Lacros-Chrome which is backed by Linux VCD instead of CrOS VCD.";
+    device_factory_ = std::make_unique<VirtualDeviceEnabledDeviceFactory>(
+        std::make_unique<DeviceFactoryMediaToMojoAdapter>(
+            std::move(video_capture_system)));
+  } else {
+    mojo::PendingRemote<crosapi::mojom::VideoCaptureDeviceFactory>
+        device_factory_ash;
+    lacros_chrome_service->BindVideoCaptureDeviceFactory(
+        device_factory_ash.InitWithNewPipeAndPassReceiver());
+    device_factory_ = std::make_unique<VirtualDeviceEnabledDeviceFactory>(
+        std::make_unique<DeviceFactoryAdapterLacros>(
+            std::move(device_factory_ash)));
+  }
 #else
   device_factory_ = std::make_unique<VirtualDeviceEnabledDeviceFactory>(
       std::make_unique<DeviceFactoryMediaToMojoAdapter>(
diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom
index 56268cc..cd26a3dc 100644
--- a/services/viz/privileged/mojom/compositing/display_private.mojom
+++ b/services/viz/privileged/mojom/compositing/display_private.mojom
@@ -63,6 +63,11 @@
   [EnableIf=is_android]
   SetSupportedRefreshRates(array<float> refresh_rates);
 
+  // Notifies associated Display to not detach child surface controls during
+  // destruction.
+  [EnableIf=is_android]
+  PreserveChildSurfaceControls();
+
   // Adds an observer that gets notified about vsync parameter changes. See
   // VSyncParameterObserver for details.
   //
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 32e0b59..1b8471c 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -4318,11 +4318,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -4332,7 +4332,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4397,11 +4397,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -4411,7 +4411,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4555,11 +4555,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -4569,7 +4569,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4634,11 +4634,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -4648,7 +4648,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4860,11 +4860,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -4874,7 +4874,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4939,11 +4939,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -4953,7 +4953,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5097,11 +5097,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -5111,7 +5111,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5176,11 +5176,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -5190,7 +5190,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 69bc713..537873cd 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46668,11 +46668,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -46682,7 +46682,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46747,11 +46747,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -46761,7 +46761,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46905,11 +46905,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -46919,7 +46919,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46984,11 +46984,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -46998,7 +46998,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47209,11 +47209,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -47223,7 +47223,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47288,11 +47288,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Client Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -47302,7 +47302,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47446,11 +47446,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.91",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 89.0.4389.95",
         "resultdb": {
           "enable": true
         },
@@ -47460,7 +47460,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M89",
-              "revision": "version:89.0.4389.91"
+              "revision": "version:89.0.4389.95"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47525,11 +47525,11 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23"
+            "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
-        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.23",
+        "name": "weblayer_instrumentation_test_versions_apk_Implementation Library Skew Tests For 90.0.4430.27",
         "resultdb": {
           "enable": true
         },
@@ -47539,7 +47539,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M90",
-              "revision": "version:90.0.4430.23"
+              "revision": "version:90.0.4430.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 25b7356..90dcd3ab 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -1502,7 +1502,7 @@
           "ignore_task_failure": false,
           "io_timeout": 21600,
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "shards": 8
         },
         "trigger_script": {
           "args": [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index b75b3f2..87f2d538 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -311,13 +311,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=90',
     ],
-    'identifier': 'Implementation Library Skew Tests For 90.0.4430.23',
+    'identifier': 'Implementation Library Skew Tests For 90.0.4430.27',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M90',
-          'revision': 'version:90.0.4430.23',
+          'revision': 'version:90.0.4430.27',
         }
       ],
     },
@@ -335,13 +335,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=89',
     ],
-    'identifier': 'Implementation Library Skew Tests For 89.0.4389.91',
+    'identifier': 'Implementation Library Skew Tests For 89.0.4389.95',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.91',
+          'revision': 'version:89.0.4389.95',
         }
       ],
     },
@@ -383,13 +383,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=90',
     ],
-    'identifier': 'Implementation Library Skew Tests For 90.0.4430.23',
+    'identifier': 'Implementation Library Skew Tests For 90.0.4430.27',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M90',
-          'revision': 'version:90.0.4430.23',
+          'revision': 'version:90.0.4430.27',
         }
       ],
     },
@@ -407,13 +407,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--impl-version=89',
     ],
-    'identifier': 'Implementation Library Skew Tests For 89.0.4389.91',
+    'identifier': 'Implementation Library Skew Tests For 89.0.4389.95',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.91',
+          'revision': 'version:89.0.4389.95',
         }
       ],
     },
@@ -455,13 +455,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=90',
     ],
-    'identifier': 'Client Library Skew Tests For 90.0.4430.23',
+    'identifier': 'Client Library Skew Tests For 90.0.4430.27',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M90',
-          'revision': 'version:90.0.4430.23',
+          'revision': 'version:90.0.4430.27',
         }
       ],
     },
@@ -479,13 +479,13 @@
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
       '--client-version=89',
     ],
-    'identifier': 'Client Library Skew Tests For 89.0.4389.91',
+    'identifier': 'Client Library Skew Tests For 89.0.4389.95',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M89',
-          'revision': 'version:89.0.4389.91',
+          'revision': 'version:89.0.4389.95',
         }
       ],
     },
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 4c23f81..ecaa1e9 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -571,6 +571,16 @@
                       'executable.', action='append',
                       dest='passthrough_args',
                       default=[])
+  parser.add_argument('--use-dynamic-shards',
+                      help='If set, use dynamic shardmap instead of the file.',
+                      action='store_true',
+                      required=False
+                      )
+  parser.add_argument('--dynamic-shardmap',
+                      help='The dynamically generated shardmap string used to '
+                      'replace the static shardmap file.',
+                      type=str,
+                      required=False)
   options, leftover_args = parser.parse_known_args(args)
   options.passthrough_args.extend(leftover_args)
   return options
@@ -609,9 +619,22 @@
         command_generator, output_paths, options.xvfb)
     test_results_files.append(output_paths.test_results)
   else:
+    if options.use_dynamic_shards:
+      shard_map_str = options.dynamic_shardmap
+      shard_map = json.loads(shard_map_str)
+      shard_map_path = os.path.join(SHARD_MAPS_DIRECTORY,
+                                    options.test_shard_map_filename)
+      with open(shard_map_path, 'w') as f:
+        json.dump(shard_map, f, indent=4, separators=(',', ': '))
+      shutil.copyfile(
+          shard_map_path,
+          os.path.join(isolated_out_dir, 'benchmarks_shard_map.json'))
+      overall_return_code = _run_benchmarks_on_shardmap(
+          shard_map, options, isolated_out_dir, test_results_files
+      )
     # If the user has supplied a list of benchmark names, execute those instead
     # of using the shard map.
-    if options.benchmarks:
+    elif options.benchmarks:
       benchmarks = options.benchmarks.split(',')
       for benchmark in benchmarks:
         output_paths = OutputFilePaths(isolated_out_dir, benchmark).SetUp()
@@ -629,7 +652,6 @@
     elif options.test_shard_map_filename:
       # First determine what shard we are running on to know how to
       # index into the bot map to get list of telemetry benchmarks to run.
-      shard_index = None
       shard_map_path = os.path.join(SHARD_MAPS_DIRECTORY,
                                     options.test_shard_map_filename)
       # Copy sharding map file to isolated_out_dir so that the merge script
@@ -639,70 +661,9 @@
           os.path.join(isolated_out_dir, 'benchmarks_shard_map.json'))
       with open(shard_map_path) as f:
         shard_map = json.load(f)
-      env = os.environ.copy()
-      if 'GTEST_SHARD_INDEX' in env:
-        shard_index = env['GTEST_SHARD_INDEX']
-      # TODO(crbug.com/972844): shard environment variables are not specified
-      # for single-shard shard runs.
-      if not shard_index:
-        shard_map_has_multiple_shards = bool(shard_map.get('1', False))
-        if not shard_map_has_multiple_shards:
-          shard_index = '0'
-      if not shard_index:
-        raise Exception(
-            'Sharded Telemetry perf tests must either specify --benchmarks '
-            'list or have GTEST_SHARD_INDEX environment variable present.')
-      shard_configuration = shard_map[shard_index]
-      assert ('benchmarks' in shard_configuration or
-              'executables' in shard_configuration), (
-                  'Every shard must have benchmarks or executables associated '
-                  'with it.')
-      if 'benchmarks' in shard_configuration:
-        benchmarks_and_configs = shard_configuration['benchmarks']
-        for (benchmark, story_selection_config
-             ) in benchmarks_and_configs.iteritems():
-          # Need to run the benchmark on both latest browser and reference
-          # build.
-          output_paths = OutputFilePaths(isolated_out_dir, benchmark).SetUp()
-          command_generator = TelemetryCommandGenerator(
-              benchmark, options,
-              story_selection_config=story_selection_config)
-          print('\n### {folder} ###'.format(folder=benchmark))
-          return_code = execute_telemetry_benchmark(
-              command_generator, output_paths, options.xvfb)
-          overall_return_code = return_code or overall_return_code
-          test_results_files.append(output_paths.test_results)
-          if options.run_ref_build:
-            reference_benchmark_foldername = benchmark + '.reference'
-            reference_output_paths = OutputFilePaths(
-                isolated_out_dir, reference_benchmark_foldername).SetUp()
-            reference_command_generator = TelemetryCommandGenerator(
-                benchmark, options,
-                story_selection_config=story_selection_config,
-                is_reference=True)
-            print('\n### {folder} ###'.format(
-                folder=reference_benchmark_foldername))
-            # We intentionally ignore the return code and test results of the
-            # reference build.
-            execute_telemetry_benchmark(
-                reference_command_generator, reference_output_paths,
-                options.xvfb)
-      if 'executables' in shard_configuration:
-        names_and_configs = shard_configuration['executables']
-        for (name, configuration
-             ) in names_and_configs.iteritems():
-          additional_flags = []
-          if 'arguments' in configuration:
-            additional_flags = configuration['arguments']
-          command_generator = GtestCommandGenerator(
-              options, override_executable=configuration['path'],
-              additional_flags=additional_flags, ignore_shard_env_vars=True)
-          output_paths = OutputFilePaths(isolated_out_dir, name).SetUp()
-          print('\n### {folder} ###'.format(folder=name))
-          return_code = execute_gtest_perf_test(
-              command_generator, output_paths, options.xvfb)
-          overall_return_code = return_code or overall_return_code
-          test_results_files.append(output_paths.test_results)
+      overall_return_code = _run_benchmarks_on_shardmap(
+          shard_map, options, isolated_out_dir, test_results_files
+      )
     else:
       raise Exception('Telemetry tests must provide either a shard map or a '
                       '--benchmarks list so that we know which stories to run.')
@@ -718,6 +679,76 @@
 
   return overall_return_code
 
+def _run_benchmarks_on_shardmap(
+    shard_map, options, isolated_out_dir, test_results_files):
+  overall_return_code = 0
+  shard_index = None
+  env = os.environ.copy()
+  if 'GTEST_SHARD_INDEX' in env:
+    shard_index = env['GTEST_SHARD_INDEX']
+  # TODO(crbug.com/972844): shard environment variables are not specified
+  # for single-shard shard runs.
+  if not shard_index:
+    shard_map_has_multiple_shards = bool(shard_map.get('1', False))
+    if not shard_map_has_multiple_shards:
+      shard_index = '0'
+  if not shard_index:
+    raise Exception(
+        'Sharded Telemetry perf tests must either specify --benchmarks '
+        'list or have GTEST_SHARD_INDEX environment variable present.')
+  shard_configuration = shard_map[shard_index]
+  assert ('benchmarks' in shard_configuration or
+          'executables' in shard_configuration), (
+      'Every shard must have benchmarks or executables associated '
+      'with it.')
+  if 'benchmarks' in shard_configuration:
+    benchmarks_and_configs = shard_configuration['benchmarks']
+    for (benchmark, story_selection_config
+         ) in benchmarks_and_configs.iteritems():
+      # Need to run the benchmark on both latest browser and reference
+      # build.
+      output_paths = OutputFilePaths(isolated_out_dir, benchmark).SetUp()
+      command_generator = TelemetryCommandGenerator(
+          benchmark, options,
+          story_selection_config=story_selection_config)
+      print('\n### {folder} ###'.format(folder=benchmark))
+      return_code = execute_telemetry_benchmark(
+          command_generator, output_paths, options.xvfb)
+      overall_return_code = return_code or overall_return_code
+      test_results_files.append(output_paths.test_results)
+      if options.run_ref_build:
+        reference_benchmark_foldername = benchmark + '.reference'
+        reference_output_paths = OutputFilePaths(
+            isolated_out_dir, reference_benchmark_foldername).SetUp()
+        reference_command_generator = TelemetryCommandGenerator(
+            benchmark, options,
+            story_selection_config=story_selection_config,
+            is_reference=True)
+        print('\n### {folder} ###'.format(
+            folder=reference_benchmark_foldername))
+        # We intentionally ignore the return code and test results of the
+        # reference build.
+        execute_telemetry_benchmark(
+            reference_command_generator, reference_output_paths,
+            options.xvfb)
+  if 'executables' in shard_configuration:
+    names_and_configs = shard_configuration['executables']
+    for (name, configuration
+         ) in names_and_configs.iteritems():
+      additional_flags = []
+      if 'arguments' in configuration:
+        additional_flags = configuration['arguments']
+      command_generator = GtestCommandGenerator(
+          options, override_executable=configuration['path'],
+          additional_flags=additional_flags, ignore_shard_env_vars=True)
+      output_paths = OutputFilePaths(isolated_out_dir, name).SetUp()
+      print('\n### {folder} ###'.format(folder=name))
+      return_code = execute_gtest_perf_test(
+          command_generator, output_paths, options.xvfb)
+      overall_return_code = return_code or overall_return_code
+      test_results_files.append(output_paths.test_results)
+
+  return overall_return_code
 
 # This is not really a "script test" so does not need to manually add
 # any additional compile targets.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c012f96..7689251 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3705,21 +3705,6 @@
             ]
         }
     ],
-    "InstantTethering": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "InstantTethering"
-                    ]
-                }
-            ]
-        }
-    ],
     "IntentBlockExternalFormRedirectsNoGesture": [
         {
             "platforms": [
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 6fce674..c01e441b 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -465,7 +465,7 @@
   ]
 
   jar_excluded_patterns = []
-  if (!is_java_debug && !dcheck_always_on) {
+  if (!enable_java_asserts) {
     # Omit the file since we use our own copy.
     jar_excluded_patterns +=
         [ "com/google/android/gms/common/internal/Preconditions.class" ]
@@ -845,7 +845,7 @@
   bypass_platform_checks = true
 
   jar_excluded_patterns = []
-  if (!is_java_debug && !dcheck_always_on) {
+  if (!enable_java_asserts) {
     # Omit the file since we use our own copy.
     jar_excluded_patterns += [ "com/google/common/base/Preconditions.class" ]
     deps += [ "//third_party/android_deps/local_modifications/preconditions:preconditions_stub_java" ]
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index 07877050..b1768f0d 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -618,7 +618,7 @@
               sb.append("""
                 |
                 | jar_excluded_patterns = []
-                | if (!is_java_debug && !dcheck_always_on) {
+                | if (!enable_java_asserts) {
                 |   # Omit the file since we use our own copy.
                 |   jar_excluded_patterns += [
                 |     "${computePreconditionsClassForDep(dependencyId)}",
diff --git a/third_party/android_deps/local_modifications/preconditions/BUILD.gn b/third_party/android_deps/local_modifications/preconditions/BUILD.gn
index ffdf7037..f7b59cd 100644
--- a/third_party/android_deps/local_modifications/preconditions/BUILD.gn
+++ b/third_party/android_deps/local_modifications/preconditions/BUILD.gn
@@ -9,12 +9,8 @@
 # uses :preconditions_stub_java
 android_library("preconditions_stub_java") {
   sources = [
+    "java/androidx/core/util/Preconditions.java",
     "java/com/google/android/gms/common/internal/Preconditions.java",
     "java/com/google/common/base/Preconditions.java",
   ]
-  deps = [ ":preconditions_androidx_stub_java" ]
-}
-
-android_library("preconditions_androidx_stub_java") {
-  sources = [ "java/androidx/core/util/Preconditions.java" ]
 }
diff --git a/third_party/android_deps/local_modifications/preconditions/javatests/org/chromium/preconditions/PreconditionsTest.java b/third_party/android_deps/local_modifications/preconditions/javatests/org/chromium/preconditions/PreconditionsTest.java
index 9de8bfb..64a7e16 100644
--- a/third_party/android_deps/local_modifications/preconditions/javatests/org/chromium/preconditions/PreconditionsTest.java
+++ b/third_party/android_deps/local_modifications/preconditions/javatests/org/chromium/preconditions/PreconditionsTest.java
@@ -35,7 +35,7 @@
     @Test
     @SmallTest
     public void testFailingPreconditionsWorkAsExpected() {
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             assertThrowsNullPointerException(() -> {
                 com.google.android.gms.common.internal.Preconditions.checkNotNull(null);
             });
diff --git a/third_party/blink/public/mojom/webdatabase/web_database.mojom b/third_party/blink/public/mojom/webdatabase/web_database.mojom
index 3d29c15d..d6e168d 100644
--- a/third_party/blink/public/mojom/webdatabase/web_database.mojom
+++ b/third_party/blink/public/mojom/webdatabase/web_database.mojom
@@ -35,10 +35,6 @@
   GetFileAttributes(mojo_base.mojom.String16 vfs_file_name) => (
       int32 attributes);
 
-  // Asks the browser process to return the size of a DB file.
-  [Sync]
-  GetFileSize(mojo_base.mojom.String16 vfs_file_name) => (int64 size);
-
   // Asks the browser set the size of a DB file.
   [Sync]
   SetFileSize(mojo_base.mojom.String16 vfs_file_name,
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index fd8b154..a51b59c 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -626,7 +626,10 @@
   // Notifies the embedder that a WebAXObject is dirty and its state needs
   // to be serialized again. If |subtree| is true, the entire subtree is
   // dirty.
-  virtual void MarkWebAXObjectDirty(const WebAXObject&, bool subtree) {}
+  virtual void MarkWebAXObjectDirty(
+      const WebAXObject&,
+      bool subtree,
+      ax::mojom::Action event_from_action = ax::mojom::Action::kNone) {}
 
   // Audio Output Devices API --------------------------------------------
 
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
index 4d23935..d917e5b 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
@@ -251,7 +251,7 @@
 
 void RendererResourceCoordinatorImpl::FireBackgroundTracingTrigger(
     const String& trigger_name) {
-  // TODO(crbug.com/1181774): Implement this.
+  DispatchFireBackgroundTracingTrigger(trigger_name);
 }
 
 RendererResourceCoordinatorImpl::RendererResourceCoordinatorImpl(
@@ -310,4 +310,19 @@
   }
 }
 
+void RendererResourceCoordinatorImpl::DispatchFireBackgroundTracingTrigger(
+    const String& trigger_name) {
+  DCHECK(service_);
+  if (!IsMainThread()) {
+    blink::PostCrossThreadTask(
+        *Thread::MainThread()->GetTaskRunner(), FROM_HERE,
+        WTF::CrossThreadBindOnce(&RendererResourceCoordinatorImpl::
+                                     DispatchFireBackgroundTracingTrigger,
+                                 WTF::CrossThreadUnretained(this),
+                                 trigger_name));
+  } else {
+    service_->FireBackgroundTracingTrigger(trigger_name);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
index 583b9abd..cec6082 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
@@ -58,6 +58,7 @@
           iframe_attribution_data);
   void DispatchOnV8ContextDetached(const blink::V8ContextToken& token);
   void DispatchOnV8ContextDestroyed(const blink::V8ContextToken& token);
+  void DispatchFireBackgroundTracingTrigger(const String& trigger_name);
 
   mojo::Remote<performance_manager::mojom::blink::ProcessCoordinationUnit>
       service_;
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
index 92185a64..3bfcafa 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
@@ -74,6 +74,10 @@
               (const blink::LocalFrameToken& parent_frame_token,
                const blink::RemoteFrameToken& remote_frame_token),
               (override));
+  MOCK_METHOD(void,
+              FireBackgroundTracingTrigger,
+              (const String& trigger_name),
+              (override));
 
   void VerifyExpectations() {
     // Ensure that any pending Mojo messages are processed.
diff --git a/third_party/blink/renderer/core/css/font_face.cc b/third_party/blink/renderer/core/css/font_face.cc
index cdf238e4..8c3664f9c 100644
--- a/third_party/blink/renderer/core/css/font_face.cc
+++ b/third_party/blink/renderer/core/css/font_face.cc
@@ -830,11 +830,6 @@
           css_font_face_, font_selector, item.GetResource()));
     }
   }
-
-  if (display_) {
-    UMA_HISTOGRAM_ENUMERATION("WebFont.FontDisplayValue",
-                              CSSValueToFontDisplay(display_.Get()));
-  }
 }
 
 void FontFace::InitCSSFontFace(const unsigned char* data, size_t size) {
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
index 3c80c87..a823e00 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
@@ -116,8 +116,7 @@
     WritingMode table_writing_mode,
     bool is_fixed_layout,
     const NGBoxStrut& cell_border,
-    const NGBoxStrut& cell_padding,
-    bool has_collapsed_borders) {
+    const NGBoxStrut& cell_padding) {
   base::Optional<LayoutUnit> css_inline_size;
   base::Optional<LayoutUnit> css_min_inline_size;
   base::Optional<LayoutUnit> css_max_inline_size;
@@ -126,43 +125,45 @@
   bool is_parallel =
       IsParallelWritingMode(table_writing_mode, style.GetWritingMode());
 
-  // Algorithm:
-  // - Compute cell's minmax sizes.
-  // - Constrain by css inline-size/max-inline-size.
+  // Be lazy when determining the min/max sizes, as in some circumstances we
+  // don't need to call this (relatively) expensive function.
+  base::Optional<MinMaxSizes> cached_min_max_sizes;
+  auto MinMaxSizesFunc = [&]() -> MinMaxSizes {
+    if (!cached_min_max_sizes) {
+      NGConstraintSpaceBuilder builder(table_writing_mode,
+                                       style.GetWritingDirection(),
+                                       /* is_new_fc */ true);
+      builder.SetTableCellBorders(cell_border);
+      builder.SetIsTableCell(true, /* is_legacy_table_cell */ false);
+      builder.SetCacheSlot(NGCacheSlot::kMeasure);
+      if (!is_parallel) {
+        // Only consider the ICB-size for the orthogonal fallback inline-size
+        // (don't use the size of the containing-block).
+        const PhysicalSize icb_size = node.InitialContainingBlockSize();
+        builder.SetOrthogonalFallbackInlineSize(
+            IsHorizontalWritingMode(table_writing_mode) ? icb_size.height
+                                                        : icb_size.width);
+      }
+      builder.SetAvailableSize({kIndefiniteSize, kIndefiniteSize});
+      const auto space = builder.ToConstraintSpace();
+
+      MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kIntrinsic);
+      cached_min_max_sizes =
+          node.ComputeMinMaxSizes(table_writing_mode, input, &space).sizes;
+    }
+
+    return *cached_min_max_sizes;
+  };
+
   InlineSizesFromStyle(style, (cell_border + cell_padding).InlineSum(),
                        is_parallel, &css_inline_size, &css_min_inline_size,
                        &css_max_inline_size, &css_percentage_inline_size);
 
-  MinMaxSizesInput input(kIndefiniteSize, MinMaxSizesType::kIntrinsic);
-  MinMaxSizesResult min_max_size;
-  bool need_constraint_space = has_collapsed_borders || !is_parallel;
-  if (need_constraint_space) {
-    NGConstraintSpaceBuilder builder(table_writing_mode,
-                                     style.GetWritingDirection(),
-                                     /* is_new_fc */ true);
-    builder.SetTableCellBorders(cell_border);
-    builder.SetIsTableCell(true, /* is_legacy_table_cell */ false);
-    builder.SetCacheSlot(NGCacheSlot::kMeasure);
-    if (!is_parallel) {
-      PhysicalSize icb_size = node.InitialContainingBlockSize();
-      builder.SetOrthogonalFallbackInlineSize(
-          IsHorizontalWritingMode(table_writing_mode) ? icb_size.height
-                                                      : icb_size.width);
-      builder.SetAvailableSize({kIndefiniteSize, kIndefiniteSize});
-    }
-    NGConstraintSpace space = builder.ToConstraintSpace();
-    // It'd be nice to avoid computing minmax if not needed, but the criteria
-    // is not clear.
-    min_max_size = node.ComputeMinMaxSizes(table_writing_mode, input, &space);
-  } else {
-    min_max_size = node.ComputeMinMaxSizes(table_writing_mode, input);
-  }
-  // Compute min inline size.
+  // Compute the resolved min inline-size.
   LayoutUnit resolved_min_inline_size;
   if (!is_fixed_layout) {
-    resolved_min_inline_size =
-        std::max(min_max_size.sizes.min_size,
-                 css_min_inline_size.value_or(LayoutUnit()));
+    resolved_min_inline_size = std::max(
+        MinMaxSizesFunc().min_size, css_min_inline_size.value_or(LayoutUnit()));
     // https://quirks.spec.whatwg.org/#the-table-cell-nowrap-minimum-width-calculation-quirk
     // Has not worked in Legacy, might be pulled out.
     if (css_inline_size && node.GetDocument().InQuirksMode()) {
@@ -177,13 +178,8 @@
     }
   }
 
-  // Compute resolved max inline size.
-  LayoutUnit content_max;
-  if (css_inline_size) {
-    content_max = *css_inline_size;
-  } else {
-    content_max = min_max_size.sizes.max_size;
-  }
+  // Compute the resolved max inline-size.
+  LayoutUnit content_max = css_inline_size.value_or(MinMaxSizesFunc().max_size);
   if (css_max_inline_size) {
     content_max = std::min(content_max, *css_max_inline_size);
     resolved_min_inline_size =
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h
index 4fec759..8c092f0 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h
@@ -209,8 +209,7 @@
       WritingMode table_writing_mode,
       bool is_fixed_layout,
       const NGBoxStrut& cell_border,
-      const NGBoxStrut& cell_padding,
-      bool has_collapsed_borders);
+      const NGBoxStrut& cell_padding);
 
   static Section CreateSection(const NGLayoutInputNode&,
                                wtf_size_t start_row,
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
index 22a29c1..7c7e4ec0 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
@@ -424,9 +424,9 @@
         NGBoxStrut cell_padding = table_borders.CellPaddingForMeasure(
             cell.Style(), table_writing_direction);
         NGTableTypes::CellInlineConstraint cell_constraint =
-            NGTableTypes::CreateCellInlineConstraint(
-                cell, table_writing_mode, is_fixed_layout, cell_border,
-                cell_padding, table_borders.IsCollapsed());
+            NGTableTypes::CreateCellInlineConstraint(cell, table_writing_mode,
+                                                     is_fixed_layout,
+                                                     cell_border, cell_padding);
         if (colspan == 1) {
           base::Optional<NGTableTypes::CellInlineConstraint>& constraint =
               (*cell_inline_constraints)[colspan_cell_tabulator
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.cc
index be9bc61..78710c3 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.cc
@@ -18,12 +18,6 @@
     const NGLayoutAlgorithmParams& params)
     : NGLayoutAlgorithm(params) {}
 
-MinMaxSizesResult NGTableRowLayoutAlgorithm::ComputeMinMaxSizes(
-    const MinMaxSizesInput&) const {
-  NOTREACHED();  // Table layout does not compute minmax for table row.
-  return MinMaxSizesResult();
-}
-
 scoped_refptr<const NGLayoutResult> NGTableRowLayoutAlgorithm::Layout() {
   const NGTableConstraintSpaceData& table_data = *ConstraintSpace().TableData();
   wtf_size_t row_index = ConstraintSpace().TableRowIndex();
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.h
index e6d8502c..4e840b5 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_row_layout_algorithm.h
@@ -23,7 +23,11 @@
 
   scoped_refptr<const NGLayoutResult> Layout() override;
 
-  MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override;
+  MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override {
+    // Table layout doesn't compute min/max sizes on table rows.
+    NOTREACHED();
+    return MinMaxSizesResult();
+  }
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
index 4e294d22..11e273b 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.cc
@@ -15,12 +15,6 @@
     const NGLayoutAlgorithmParams& params)
     : NGLayoutAlgorithm(params) {}
 
-MinMaxSizesResult NGTableSectionLayoutAlgorithm::ComputeMinMaxSizes(
-    const MinMaxSizesInput&) const {
-  NOTREACHED();  // Table layout does not compute minmax for table row.
-  return MinMaxSizesResult();
-}
-
 // Generated fragment structure:
 // +-----section--------------+
 // |       vspacing           |
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.h
index 0ecbb790..e14f2cb 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_section_layout_algorithm.h
@@ -23,7 +23,11 @@
 
   scoped_refptr<const NGLayoutResult> Layout() override;
 
-  MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override;
+  MinMaxSizesResult ComputeMinMaxSizes(const MinMaxSizesInput&) const override {
+    // Table layout doesn't compute min/max sizes on table sections.
+    NOTREACHED();
+    return MinMaxSizesResult();
+  }
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/DEPS b/third_party/blink/renderer/modules/DEPS
index 5018445..9c9c62f 100644
--- a/third_party/blink/renderer/modules/DEPS
+++ b/third_party/blink/renderer/modules/DEPS
@@ -41,4 +41,7 @@
     "canvas_fuzzer.cc": [
         "+base/test/bind.h",
     ],
+    "web_ax_object.cc": [
+        "+ui/accessibility/ax_action_data.h",
+    ],
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index eb17965..e76c4598 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1269,7 +1269,7 @@
 
   tree_update_callback_queue_.push_back(MakeGarbageCollected<TreeUpdateParams>(
       obj->GetNode(), obj->AXObjectID(), ComputeEventFrom(),
-      ActiveEventIntents(), std::move(callback)));
+      active_event_from_action_, ActiveEventIntents(), std::move(callback)));
 
   // These events are fired during DocumentLifecycle::kInAccessibility,
   // ensure there is a document lifecycle update scheduled.
@@ -1310,7 +1310,8 @@
 #endif
 
   tree_update_callback_queue_.push_back(MakeGarbageCollected<TreeUpdateParams>(
-      node, 0, ComputeEventFrom(), ActiveEventIntents(), std::move(callback)));
+      node, 0, ComputeEventFrom(), active_event_from_action_,
+      ActiveEventIntents(), std::move(callback)));
 
   // These events are fired during DocumentLifecycle::kInAccessibility,
   // ensure there is a document lifecycle update scheduled.
@@ -1923,14 +1924,15 @@
     if (document != tree_update_document) {
       tree_update_callback_queue_.push_back(
           MakeGarbageCollected<TreeUpdateParams>(
-              node, axid, tree_update->event_from, tree_update->event_intents,
+              node, axid, tree_update->event_from,
+              tree_update->event_from_action, tree_update->event_intents,
               std::move(callback)));
       continue;
     }
 
-    FireTreeUpdatedEventImmediately(document, tree_update->event_from,
-                                    tree_update->event_intents,
-                                    std::move(callback));
+    FireTreeUpdatedEventImmediately(
+        document, tree_update->event_from, tree_update->event_from_action,
+        tree_update->event_intents, std::move(callback));
   }
 }
 
@@ -1948,14 +1950,16 @@
 
     ax::mojom::blink::Event event_type = params->event_type;
     ax::mojom::blink::EventFrom event_from = params->event_from;
+    ax::mojom::blink::Action event_from_action = params->event_from_action;
     const BlinkAXEventIntentsSet& event_intents = params->event_intents;
     if (obj->GetDocument() != &document) {
       notifications_to_post_.push_back(MakeGarbageCollected<AXEventParams>(
-          obj, event_type, event_from, event_intents));
+          obj, event_type, event_from, event_from_action, event_intents));
       continue;
     }
 
-    FireAXEventImmediately(obj, event_type, event_from, event_intents);
+    FireAXEventImmediately(obj, event_type, event_from, event_from_action,
+                           event_intents);
   }
 }
 
@@ -1994,12 +1998,13 @@
   if (object->GetDocument()->Lifecycle().GetState() ==
       DocumentLifecycle::kInAccessibility) {
     FireAXEventImmediately(object, event_type, ComputeEventFrom(),
-                           ActiveEventIntents());
+                           active_event_from_action_, ActiveEventIntents());
     return;
   }
 
   notifications_to_post_.push_back(MakeGarbageCollected<AXEventParams>(
-      object, event_type, ComputeEventFrom(), ActiveEventIntents()));
+      object, event_type, ComputeEventFrom(), active_event_from_action_,
+      ActiveEventIntents()));
 
   // These events are fired during DocumentLifecycle::kInAccessibility,
   // ensure there is a visual update scheduled.
@@ -2031,6 +2036,7 @@
 void AXObjectCacheImpl::FireTreeUpdatedEventImmediately(
     Document& document,
     ax::mojom::blink::EventFrom event_from,
+    ax::mojom::blink::Action event_from_action,
     const BlinkAXEventIntentsSet& event_intents,
     base::OnceClosure callback) {
   DCHECK_EQ(document.Lifecycle().GetState(),
@@ -2038,6 +2044,8 @@
 
   base::AutoReset<ax::mojom::blink::EventFrom> event_from_resetter(
       &active_event_from_, event_from);
+  base::AutoReset<ax::mojom::blink::Action> event_from_action_resetter(
+      &active_event_from_action_, event_from_action);
   ScopedBlinkAXEventIntent defered_event_intents(event_intents.AsVector(),
                                                  &document);
   std::move(callback).Run();
@@ -2047,6 +2055,7 @@
     AXObject* obj,
     ax::mojom::blink::Event event_type,
     ax::mojom::blink::EventFrom event_from,
+    ax::mojom::blink::Action event_from_action,
     const BlinkAXEventIntentsSet& event_intents) {
   DCHECK_EQ(obj->GetDocument()->Lifecycle().GetState(),
             DocumentLifecycle::kInAccessibility);
@@ -2064,7 +2073,8 @@
   SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*obj->GetDocument());
 #endif  // DCHECK_IS_ON()
 
-  PostPlatformNotification(obj, event_type, event_from, event_intents);
+  PostPlatformNotification(obj, event_type, event_from, event_from_action,
+                           event_intents);
 
   if (event_type == ax::mojom::blink::Event::kChildrenChanged &&
       obj->CachedParentObject()) {
@@ -2708,6 +2718,7 @@
     AXObject* obj,
     ax::mojom::blink::Event event_type,
     ax::mojom::blink::EventFrom event_from,
+    ax::mojom::blink::Action event_from_action,
     const BlinkAXEventIntentsSet& event_intents) {
   if (!document_ || !document_->View() ||
       !document_->View()->GetFrame().GetPage()) {
@@ -2721,6 +2732,7 @@
     event.id = obj->AXObjectID();
     event.event_type = event_type;
     event.event_from = event_from;
+    event.event_from_action = event_from_action;
     event.event_intents.resize(event_intents.size());
     // We need to filter out the counts from every intent.
     std::transform(event_intents.begin(), event_intents.end(),
@@ -2731,7 +2743,10 @@
   }
 }
 
-void AXObjectCacheImpl::MarkAXObjectDirtyHelper(AXObject* obj, bool subtree) {
+void AXObjectCacheImpl::MarkAXObjectDirtyHelper(
+    AXObject* obj,
+    bool subtree,
+    ax::mojom::blink::Action event_from_action) {
   if (!obj || obj->IsDetached() || !obj->GetDocument() ||
       !obj->GetDocument()->View() ||
       !obj->GetDocument()->View()->GetFrame().GetPage()) {
@@ -2740,15 +2755,19 @@
 
   WebLocalFrameImpl* webframe = WebLocalFrameImpl::FromFrame(
       obj->GetDocument()->AXObjectCacheOwner().GetFrame());
-  if (webframe && webframe->Client())
-    webframe->Client()->MarkWebAXObjectDirty(WebAXObject(obj), subtree);
+  if (webframe && webframe->Client()) {
+    webframe->Client()->MarkWebAXObjectDirty(WebAXObject(obj), subtree,
+                                             event_from_action);
+  }
 }
 
-void AXObjectCacheImpl::MarkAXObjectDirtyWithCleanLayout(AXObject* obj,
-                                                         bool subtree) {
+void AXObjectCacheImpl::MarkAXObjectDirtyWithCleanLayout(
+    AXObject* obj,
+    bool subtree,
+    ax::mojom::blink::Action event_from_action) {
   if (!obj)
     return;
-  MarkAXObjectDirtyHelper(obj, subtree);
+  MarkAXObjectDirtyHelper(obj, subtree, event_from_action);
   UpdateCachedAttributeValuesWithCleanLayout(obj->GetNode(), obj);
 }
 
@@ -2759,10 +2778,13 @@
     obj->UpdateCachedAttributeValuesIfNeeded(true);
 }
 
-void AXObjectCacheImpl::MarkAXObjectDirty(AXObject* obj, bool subtree) {
+void AXObjectCacheImpl::MarkAXObjectDirty(
+    AXObject* obj,
+    bool subtree,
+    ax::mojom::blink::Action event_from_action) {
   if (!obj)
     return;
-  MarkAXObjectDirtyHelper(obj, subtree);
+  MarkAXObjectDirtyHelper(obj, subtree, event_from_action);
   if (obj->GetNode()) {
     DeferTreeUpdate(
         &AXObjectCacheImpl::UpdateCachedAttributeValuesWithCleanLayout, obj);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index b7b4ac0..af9c2c0 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -250,7 +250,11 @@
   // TODO(accessibility) Find out if we can merge with EnsurePostNotification().
   void PostNotification(Node*, ax::mojom::blink::Event);
   void PostNotification(AXObject*, ax::mojom::blink::Event);
-  void MarkAXObjectDirtyWithCleanLayout(AXObject*, bool subtree);
+  void MarkAXObjectDirtyWithCleanLayout(
+      AXObject*,
+      bool subtree,
+      ax::mojom::blink::Action event_from_action =
+          ax::mojom::blink::Action::kNone);
 
   //
   // Aria-owns support.
@@ -291,11 +295,16 @@
   WebAXAutofillState GetAutofillState(AXID id) const;
   void SetAutofillState(AXID id, WebAXAutofillState state);
 
-  ax::mojom::blink::EventFrom active_event_from() const {
-    return active_event_from_;
+  std::pair<ax::mojom::blink::EventFrom, ax::mojom::blink::Action>
+  active_event_from_data() const {
+    return std::make_pair(active_event_from_, active_event_from_action_);
   }
-  void set_active_event_from(const ax::mojom::blink::EventFrom event_from) {
+
+  void set_active_event_from_data(
+      const ax::mojom::blink::EventFrom event_from,
+      const ax::mojom::blink::Action event_from_action) {
     active_event_from_ = event_from;
+    active_event_from_action_ = event_from_action;
   }
 
   AXObject* GetActiveAriaModalDialog() const;
@@ -319,6 +328,8 @@
       ax::mojom::blink::Event event_type,
       ax::mojom::blink::EventFrom event_from =
           ax::mojom::blink::EventFrom::kNone,
+      ax::mojom::blink::Action event_from_action =
+          ax::mojom::blink::Action::kNone,
       const BlinkAXEventIntentsSet& event_intents = BlinkAXEventIntentsSet());
   void LabelChangedWithCleanLayout(Element*);
 
@@ -353,8 +364,12 @@
     AXEventParams(AXObject* target,
                   ax::mojom::blink::Event event_type,
                   ax::mojom::blink::EventFrom event_from,
+                  ax::mojom::blink::Action event_from_action,
                   const BlinkAXEventIntentsSet& intents)
-        : target(target), event_type(event_type), event_from(event_from) {
+        : target(target),
+          event_type(event_type),
+          event_from(event_from),
+          event_from_action(event_from_action) {
       for (const auto& intent : intents) {
         event_intents.insert(intent.key, intent.value);
       }
@@ -362,6 +377,7 @@
     Member<AXObject> target;
     ax::mojom::blink::Event event_type;
     ax::mojom::blink::EventFrom event_from;
+    ax::mojom::blink::Action event_from_action;
     BlinkAXEventIntentsSet event_intents;
 
     void Trace(Visitor* visitor) const { visitor->Trace(target); }
@@ -371,11 +387,13 @@
     TreeUpdateParams(const Node* node,
                      AXID axid,
                      ax::mojom::blink::EventFrom event_from,
+                     ax::mojom::blink::Action event_from_action,
                      const BlinkAXEventIntentsSet& intents,
                      base::OnceClosure callback)
         : node(node),
           axid(axid),
           event_from(event_from),
+          event_from_action(event_from_action),
           callback(std::move(callback)) {
       for (const auto& intent : intents) {
         event_intents.insert(intent.key, intent.value);
@@ -384,6 +402,7 @@
     WeakMember<const Node> node;
     AXID axid;
     ax::mojom::blink::EventFrom event_from;
+    ax::mojom::blink::Action event_from_action;
     BlinkAXEventIntentsSet event_intents;
     base::OnceClosure callback;
 
@@ -393,8 +412,13 @@
   ax::mojom::blink::EventFrom ComputeEventFrom();
 
   void UpdateCachedAttributeValuesWithCleanLayout(Node* node, AXObject* obj);
-  void MarkAXObjectDirtyHelper(AXObject* obj, bool subtree);
-  void MarkAXObjectDirty(AXObject*, bool subtree);
+  void MarkAXObjectDirtyHelper(AXObject* obj,
+                               bool subtree,
+                               ax::mojom::blink::Action event_from_action);
+  void MarkAXObjectDirty(AXObject*,
+                         bool subtree,
+                         ax::mojom::blink::Action event_from_action =
+                             ax::mojom::blink::Action::kNone);
   void MarkElementDirty(const Node*, bool subtree);
   void MarkAXSubtreeDirtyWithCleanLayout(AXObject*);
   void MarkElementDirtyWithCleanLayout(const Node*, bool subtree);
@@ -533,11 +557,13 @@
   void FireTreeUpdatedEventImmediately(
       Document& document,
       ax::mojom::blink::EventFrom event_from,
+      ax::mojom::blink::Action event_from_action,
       const BlinkAXEventIntentsSet& event_intents,
       base::OnceClosure callback);
   void FireAXEventImmediately(AXObject* obj,
                               ax::mojom::blink::Event event_type,
                               ax::mojom::blink::EventFrom event_from,
+                              ax::mojom::blink::Action event_from_action,
                               const BlinkAXEventIntentsSet& event_intents);
 
   void SetMaxPendingUpdatesForTesting(wtf_size_t max_pending_updates) {
@@ -589,6 +615,11 @@
   ax::mojom::blink::EventFrom active_event_from_ =
       ax::mojom::blink::EventFrom::kNone;
 
+  // The accessibility action that caused the event. Will only be valid if
+  // active_event_from_ is set to kAction.
+  ax::mojom::blink::Action active_event_from_action_ =
+      ax::mojom::blink::Action::kNone;
+
   // A set of currently active event intents.
   BlinkAXEventIntentsSet active_event_intents_;
 
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 0bffce8..dfcbd2cd 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -58,6 +58,7 @@
 #include "third_party/blink/renderer/modules/accessibility/ax_selection.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/skia/include/core/SkMatrix44.h"
+#include "ui/accessibility/ax_action_data.h"
 
 namespace blink {
 
@@ -88,15 +89,22 @@
 // AXObjCache or AXObjectCacheImpl handles programmatic actions.
 class ScopedActionAnnotator {
  public:
-  explicit ScopedActionAnnotator(AXObject* obj)
+  ScopedActionAnnotator(AXObject* obj,
+                        ax::mojom::blink::Action event_from_action)
       : cache_(&obj->AXObjectCache()) {
-    DCHECK_EQ(cache_->active_event_from(), ax::mojom::blink::EventFrom::kNone)
+    std::pair<ax::mojom::blink::EventFrom, ax::mojom::blink::Action>
+        event_from_data = cache_->active_event_from_data();
+    DCHECK_EQ(event_from_data.first, ax::mojom::blink::EventFrom::kNone)
         << "Multiple ScopedActionAnnotator instances cannot be nested.";
-    cache_->set_active_event_from(ax::mojom::blink::EventFrom::kAction);
+    DCHECK_EQ(event_from_data.second, ax::mojom::blink::Action::kNone)
+        << "event_from_action must not be set before construction.";
+    cache_->set_active_event_from_data(ax::mojom::blink::EventFrom::kAction,
+                                       event_from_action);
   }
 
   ~ScopedActionAnnotator() {
-    cache_->set_active_event_from(ax::mojom::blink::EventFrom::kNone);
+    cache_->set_active_event_from_data(ax::mojom::blink::EventFrom::kNone,
+                                       ax::mojom::blink::Action::kNone);
   }
 
  private:
@@ -517,7 +525,8 @@
   if (IsDetached())
     return WebAXObject();
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(private_.Get(),
+                                  ax::mojom::blink::Action::kHitTest);
   IntPoint contents_point =
       private_->DocumentFrameView()->SoonToBeRemovedUnscaledViewportToContents(
           IntPoint(point));
@@ -599,7 +608,7 @@
   if (IsDetached())
     return false;  // Updating lifecycle could detach object.
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(private_.Get(), action_data.action);
   return private_->PerformAction(action_data);
 }
 
@@ -718,7 +727,8 @@
   if (IsDetached())
     return false;
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(private_.Get(),
+                                  ax::mojom::blink::Action::kSetSelection);
   return private_->RequestSetSelectedAction(selected);
 }
 
@@ -729,7 +739,8 @@
   if (IsDetached() || anchor_object.IsDetached() || focus_object.IsDetached())
     return false;
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(private_.Get(),
+                                  ax::mojom::blink::Action::kSetSelection);
   AXPosition ax_base, ax_extent;
   if (static_cast<const AXObject*>(anchor_object)->IsTextObject() ||
       static_cast<const AXObject*>(anchor_object)->IsNativeTextControl()) {
@@ -1209,7 +1220,8 @@
   if (IsDetached())
     return false;
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(
+      private_.Get(), ax::mojom::blink::Action::kScrollToMakeVisible);
   return private_->RequestScrollToMakeVisibleAction();
 }
 
@@ -1221,7 +1233,8 @@
   if (IsDetached())
     return false;
 
-  ScopedActionAnnotator annotater(private_.Get());
+  ScopedActionAnnotator annotater(
+      private_.Get(), ax::mojom::blink::Action::kScrollToMakeVisible);
   auto horizontal_behavior =
       ToBlinkScrollAlignmentBehavior(horizontal_scroll_alignment);
   auto vertical_behavior =
diff --git a/third_party/blink/renderer/modules/webdatabase/web_database_host.cc b/third_party/blink/renderer/modules/webdatabase/web_database_host.cc
index 8733e44..3581f00 100644
--- a/third_party/blink/renderer/modules/webdatabase/web_database_host.cc
+++ b/third_party/blink/renderer/modules/webdatabase/web_database_host.cc
@@ -63,12 +63,6 @@
   return rv;
 }
 
-int64_t WebDatabaseHost::GetFileSize(const String& vfs_file_name) {
-  int64_t rv = 0LL;
-  GetWebDatabaseHost().GetFileSize(vfs_file_name, &rv);
-  return rv;
-}
-
 bool WebDatabaseHost::SetFileSize(const String& vfs_file_name, int64_t size) {
   bool rv = false;
   GetWebDatabaseHost().SetFileSize(vfs_file_name, size, &rv);
diff --git a/third_party/blink/renderer/modules/webdatabase/web_database_host.h b/third_party/blink/renderer/modules/webdatabase/web_database_host.h
index 5be418bd..b459cd0 100644
--- a/third_party/blink/renderer/modules/webdatabase/web_database_host.h
+++ b/third_party/blink/renderer/modules/webdatabase/web_database_host.h
@@ -47,7 +47,6 @@
   base::File OpenFile(const String& vfs_file_name, int desired_flags);
   int DeleteFile(const String& vfs_file_name, bool sync_dir);
   int32_t GetFileAttributes(const String& vfs_file_name);
-  int64_t GetFileSize(const String& vfs_file_name);
   bool SetFileSize(const String& vfs_file_name, int64_t size);
   int64_t GetSpaceAvailableForOrigin(const SecurityOrigin& origin);
 
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/DIR_METADATA b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/DIR_METADATA
deleted file mode 100644
index 563fa67..0000000
--- a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/DIR_METADATA
+++ /dev/null
@@ -1,6 +0,0 @@
-monorail {
-  component: "Blink>MediaStream"
-}
-wpt {
-  notify: YES
-}
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-audio.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-audio.https.html
deleted file mode 100644
index 8fdb353..0000000
--- a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-audio.https.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<!doctype html>
-<html>
-
-<head>
-  <title>MediaStreamTrackGenerator</title>
-  <link rel="help" href="https://w3c.github.io/mediacapture-insertable-streams">
-</head>
-
-<body>
-  <p class="instructions">When prompted, use the accept button to give permission to use your audio and video devices.</p>
-  <h1 class="instructions">Description</h1>
-  <p class="instructions">This test checks that generating audio MediaStreamTracks works as expected.</p>
-  <audio id="audioElement" autoplay=true></audio>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script>
-
-    function makeAudioFrame(timestamp) {
-      const sampleRate = 3000;
-      const buffer = new AudioBuffer({
-        length: sampleRate / 10, // 100ms
-        numberOfChannels: 1,
-        sampleRate: sampleRate
-      });
-      // Generate a simple sin wave, so we have something.
-      const array = buffer.getChannelData(0);
-      const hz = 100; // sound frequency
-      for (let i = 0; i < array.length; i++) {
-        const t = (i / sampleRate) * hz * (Math.PI * 2);
-        array[i] = Math.sin(t);
-      }
-
-      return new AudioFrame({
-        timestamp: timestamp,
-        buffer: buffer
-      });
-    }
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator("audio");
-
-      const writer = generator.writable.getWriter();
-      await writer.write(makeAudioFrame(1));
-
-      assert_equals(generator.kind, "audio");
-      assert_equals(generator.readyState, "live");
-
-      t.add_cleanup(() => generator.stop());
-    }, "Tests that creating a Audio MediaStreamTrackGenerator works as expected");
-
-    promise_test(async t => {
-      const capturedStream = await navigator.mediaDevices.getUserMedia({ audio: true });
-      assert_equals(capturedStream.getAudioTracks().length, 1);
-      const upstreamTrack = capturedStream.getAudioTracks()[0];
-      t.add_cleanup(() => upstreamTrack.stop());
-
-      const generator = new MediaStreamTrackGenerator({ signalTarget: upstreamTrack, kind: "audio" });
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeAudioFrame(1);
-      await writer.write(frame);
-
-      assert_equals(generator.kind, "audio");
-      assert_equals(generator.readyState, "live");
-    }, "Tests that creating an Audio MediaStreamTrackGenerator with a signal target works as expected");
-
-    promise_test(async t => {
-      assert_throws_js(TypeError, () => { new MediaStreamTrackGenerator({ kind: "invalid kind" }) });
-    }, "Creating Generator with an invalid kind throws");
-
-    promise_test(async t => {
-      const capturedStream = await navigator.mediaDevices.getUserMedia({ audio: true });
-      assert_equals(capturedStream.getAudioTracks().length, 1);
-      const upstreamTrack = capturedStream.getAudioTracks()[0];
-      t.add_cleanup(() => upstreamTrack.stop());
-
-      assert_throws_js(TypeError, () => { new MediaStreamTrackGenerator({ signalTarget: upstreamTrack }) });
-    }, "Creating Generator with a missing kind throws");
-
-    promise_test(async t => {
-      const capturedStream = await navigator.mediaDevices.getUserMedia({ audio: true });
-      assert_equals(capturedStream.getAudioTracks().length, 1);
-      const upstreamTrack = capturedStream.getAudioTracks()[0];
-      t.add_cleanup(() => upstreamTrack.stop());
-
-      assert_throws_js(TypeError, () => { new MediaStreamTrackGenerator({ signalTarget: upstreamTrack, kind: "video" }) });
-    }, "Creating Generator with mismatched kinds throws");
-
-    promise_test(async t => {
-      assert_throws_js(TypeError, () => { new MediaStreamTrackGenerator({ signalTarget: "IamNotATrack" }) });
-    }, "Creating Generator with invalid signalTarget throws");
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator({ kind: "video" });
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeAudioFrame(1);
-
-      writer.write(frame).then(t.step_func(() => assert_unreached("Write should reject")), t.step_func(f => assert_true(f instanceof TypeError, "write rejects with a TypeError")));
-    }, "Mismatched frame and generator kind throws on write.");
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator("audio");
-      t.add_cleanup(() => generator.stop());
-
-      const audioElement = document.getElementById("audioElement");
-      audioElement.srcObject = new MediaStream([generator]);
-      await audioElement.play();
-
-      const writer = generator.writable.getWriter();
-      await writer.write(makeAudioFrame(1));
-
-      // Wait for audio playout to actually happen.
-      await t.step_wait(() => audioElement.currentTime > 0, "audioElement played out generated track");
-    }, "Tests that audio actually flows to a connected audio element");
-  </script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-video.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-video.https.html
index a1289b60..e1d1565d 100644
--- a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-video.https.html
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-video.https.html
@@ -6,152 +6,42 @@
 <script src="/resources/testharnessreport.js"></script>
 </head>
 <body>
-  <p class="instructions">When prompted, use the accept button to give permission to use your audio and video devices.</p>
-  <h1 class="instructions">Description</h1>
-  <p class="instructions">This test checks that generating video MediaStreamTracks works as expected.</p>
-  <script>
+<script>
 
-    const pixelColour = [50, 100, 150, 255];
-    function makeVideoFrame(timestamp) {
-      const height = 240;
-      const width = 320;
-      const canvas = new OffscreenCanvas(width, height);
+async function getVideoFrame() {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  const input_track = stream.getTracks()[0];
+  const processor = new MediaStreamTrackProcessor(input_track);
+  const reader = processor.readable.getReader();
+  const result = await reader.read();
+  input_track.stop();
+  return result.value;
+}
 
-      const ctx = canvas.getContext('2d', { alpha: false });
-      ctx.fillStyle = `rgba(${pixelColour[0]}, ${pixelColour[1]}, ${pixelColour[2]}, ${pixelColour[3]})`;
-      ctx.fillRect(0, 0, width, height);
+promise_test(async t => {
+  const videoFrame = await getVideoFrame();
+  const originalWidth = videoFrame.displayWidth;
+  const originalHeight = videoFrame.displayHeight;
+  const originalTimestamp = videoFrame.timestamp;
+  const generator = new MediaStreamTrackGenerator({kind: 'video'});
 
-      return new VideoFrame(canvas.transferToImageBitmap(), { timestamp });
-    }
+  // Use a MediaStreamTrackProcessor as a sink for |generator| to verify
+  // that |processor| actually forwards the frames written to its writable
+  // field.
+  const processor = new MediaStreamTrackProcessor(generator);
+  const reader = processor.readable.getReader();
+  const readerPromise = new Promise(async resolve => {
+    const result = await reader.read();
+    assert_equals(result.value.displayWidth, originalWidth);
+    assert_equals(result.value.displayHeight, originalHeight);
+    assert_equals(result.value.timestamp, originalTimestamp);
+    resolve();
+  });
 
-    async function getVideoFrame() {
-      const stream = await navigator.mediaDevices.getUserMedia({video: true});
-      const input_track = stream.getTracks()[0];
-      const processor = new MediaStreamTrackProcessor(input_track);
-      const reader = processor.readable.getReader();
-      const result = await reader.read();
-      input_track.stop();
-      return result.value;
-    }
+  generator.writable.getWriter().write(videoFrame);
+  return readerPromise;
+}, 'MediaStreamTrackGenerator forwards frames to sink');
 
-    function assertPixel(t, bytes, expected) {
-      for (let i = 0; i < bytes.length; i++) {
-        t.step(() => {
-          assert_less_than(bytes[i], expected[i] + 2, "Mismatched pixel");
-          assert_greater_than(bytes[i], expected[i] - 2, "Mismatched pixel");
-        });
-      }
-    }
-
-    promise_test(async t => {
-      const videoFrame = await getVideoFrame();
-      const originalWidth = videoFrame.displayWidth;
-      const originalHeight = videoFrame.displayHeight;
-      const originalTimestamp = videoFrame.timestamp;
-      const generator = new MediaStreamTrackGenerator({kind: 'video'});
-      t.add_cleanup(() => generator.stop());
-
-      // Use a MediaStreamTrackProcessor as a sink for |generator| to verify
-      // that |processor| actually forwards the frames written to its writable
-      // field.
-      const processor = new MediaStreamTrackProcessor(generator);
-      const reader = processor.readable.getReader();
-      const readerPromise = new Promise(async resolve => {
-        const result = await reader.read();
-        assert_equals(result.value.displayWidth, originalWidth);
-        assert_equals(result.value.displayHeight, originalHeight);
-        assert_equals(result.value.timestamp, originalTimestamp);
-        resolve();
-      });
-
-      generator.writable.getWriter().write(videoFrame);
-
-      return readerPromise;
-    }, 'Tests that MediaStreamTrackGenerator forwards frames to sink');
-
-    promise_test(async t => {
-      const videoFrame = makeVideoFrame(1);
-      const originalWidth = videoFrame.displayWidth;
-      const originalHeight = videoFrame.displayHeight;
-      const originalTimestamp = videoFrame.timestamp;
-      const generator = new MediaStreamTrackGenerator({kind: 'video'});
-      t.add_cleanup(() => generator.stop());
-
-      const video = document.createElement("video");
-      video.autoplay = true;
-      video.width = 320;
-      video.height = 240;
-      video.srcObject = new MediaStream([generator]);
-      video.play();
-
-      // Allow async setup of the track generator and stream.
-      await new Promise(r => t.step_timeout(r, 1));
-
-      await generator.writable.getWriter().write(videoFrame);
-
-      await t.step_wait(() => video.currentTime > 0, "video has played");
-
-
-      const canvas = document.createElement("canvas");
-      canvas.width = originalWidth;
-      canvas.height = originalHeight;
-      const context = canvas.getContext('2d');
-      context.drawImage(video, 0, 0);
-      // Pick a pixel in the centre of the video and check that it has the colour of the frame provided.
-      const pixel = context.getImageData(videoFrame.displayWidth/2, videoFrame.displayHeight/2, 1, 1);
-      assertPixel(t, pixel.data, pixelColour);
-    }, 'Tests that frames are actually rendered correctly in a stream used for a video element.');
-
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator("video");
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeVideoFrame(1);
-      await writer.write(frame);
-
-      assert_equals(generator.kind, "video");
-      assert_equals(generator.readyState, "live");
-    }, "Tests that creating a Video MediaStreamTrackGenerator works as expected");
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator("video");
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeVideoFrame(1);
-      await writer.write(frame);
-
-      assert_throws_dom("InvalidStateError", () => frame.clone(), "VideoFrame wasn't destroyed on write.");
-    }, "Tests that VideoFrames are destroyed on write.");
-
-    promise_test(async t => {
-      const capturedStream = await navigator.mediaDevices.getUserMedia({ video: true });
-      assert_equals(capturedStream.getVideoTracks().length, 1);
-      const upstreamTrack = capturedStream.getVideoTracks()[0];
-      t.add_cleanup(() => upstreamTrack.stop());
-
-      const generator = new MediaStreamTrackGenerator({ signalTarget: upstreamTrack, kind: "video" });
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeVideoFrame(1);
-      await writer.write(frame);
-
-      assert_equals(generator.kind, "video");
-      assert_equals(generator.readyState, "live");
-    }, "Tests that creating a Video MediaStreamTrackGenerator with a signal target works as expected");
-
-
-    promise_test(async t => {
-      const generator = new MediaStreamTrackGenerator("audio");
-      t.add_cleanup(() => generator.stop());
-
-      const writer = generator.writable.getWriter();
-      const frame = makeVideoFrame(1);
-      assert_throws_js(TypeError, writer.write(frame));
-    }, "Mismatched frame and generator kind throws on write.");
-  </script>
+</script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-audio.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-audio.https.html
deleted file mode 100644
index 8487f7d..0000000
--- a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-audio.https.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!doctype html>
-<html>
-
-<head>
-  <title>MediaStreamTrackProcessor</title>
-  <link rel="help" href="https://w3c.github.io/mediacapture-insertable-streams">
-</head>
-
-<body>
-  <p class="instructions">When prompted, use the accept button to give permission to use your audio and video devices.</p>
-  <h1 class="instructions">Description</h1>
-  <p class="instructions">This test checks that processing captured audio MediaStreamTracks works as expected.</p>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script>
-
-    promise_test(async t => {
-      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
-      assert_equals(stream.getAudioTracks().length, 1);
-      const audioTrack = stream.getAudioTracks()[0];
-
-      return new Promise((resolve, reject) => {
-        const writableStream = new WritableStream({
-          write(audioFrame) {
-            assert_true(audioFrame instanceof AudioFrame);
-            assert_not_equals(audioFrame.timestamp, null);
-            resolve();
-          },
-          close() {
-            assert_unreached("Closed");
-          },
-          abort(err) {
-            assert_unreached("Sink error:" + err);
-          }
-        });
-        const audioTrackProcessor = new MediaStreamTrackProcessor(audioTrack);
-        audioTrackProcessor.readable.pipeTo(writableStream);
-      });
-    }, "Tests that creating an Audio MediaStreamTrackProcessor works as expected");
-  </script>
-</body>
-
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-video.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-video.https.html
deleted file mode 100644
index 5829f3c..0000000
--- a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackProcessor-video.https.html
+++ /dev/null
@@ -1,106 +0,0 @@
-<!doctype html>
-<html>
-
-<head>
-  <title>MediaStreamTrackProcessor</title>
-  <link rel="help" href="https://w3c.github.io/mediacapture-insertable-streams">
-</head>
-
-<body>
-  <p class="instructions">When prompted, use the accept button to give permission to use your audio and video devices.</p>
-  <h1 class="instructions">Description</h1>
-  <p class="instructions">This test checks that processing captured video MediaStreamTracks works as expected.</p>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <canvas id="canvas"></canvas>
-  <script>
-    const pixelColour = [50, 100, 150, 255];
-    function makeVideoFrame(timestamp) {
-      const height = 240;
-      const width = 320;
-      const canvas = new OffscreenCanvas(width, height);
-
-      const ctx = canvas.getContext('2d');
-      ctx.fillStyle = `rgba(${pixelColour[0]}, ${pixelColour[1]}, ${pixelColour[2]}, ${pixelColour[3]})`;
-      ctx.fillRect(0, 0, width, height);
-
-      return new VideoFrame(canvas.transferToImageBitmap(), { timestamp });
-    }
-
-    // Assert that a pixel, in RGBA bytes, approximately matches the expected value.
-    function assertPixel(t, bytes, expected) {
-      t.step(() => {
-        assert_equals(bytes.length, expected.length, "pixel bytes not correct length")
-        for (let i = 0; i < bytes.length; i++) {
-          assert_less_than(bytes[i], expected[i] + 2, "Mismatched pixel");
-          assert_greater_than(bytes[i], expected[i] - 2, "Mismatched pixel");
-        }
-      });
-    }
-
-    promise_test(async t => {
-      const height = 240;
-      const width = 320;
-      const canvas = document.getElementById('canvas');
-      canvas.width = width;
-      canvas.height = height;
-
-      const ctx = canvas.getContext('2d');
-      ctx.fillStyle = `rgba(${pixelColour[0]}, ${pixelColour[1]}, ${pixelColour[2]}, ${pixelColour[3]})`;
-      ctx.fillRect(0, 0, width, height);
-
-
-      const stream = canvas.captureStream(10);
-      assert_equals(stream.getVideoTracks().length, 1);
-      const videoTrack = stream.getVideoTracks()[0];
-      t.add_cleanup(() => videoTrack.stop());
-
-      return new Promise(async (resolve, reject) => {
-        const writableStream = new WritableStream({
-          write(videoFrame) {
-            t.step(() => {
-              assert_true(videoFrame instanceof VideoFrame);
-              assert_equals(videoFrame.codedWidth, 320);
-              assert_not_equals(videoFrame.timestamp, null);
-            });
-
-            videoFrame.createImageBitmap().then(bitmap => {
-              const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
-              const canvasCtx = canvas.getContext('2d');
-              canvasCtx.drawImage(bitmap, 0, 0);
-
-              // Check the provided frame matches the canvas input.
-              const imgData = canvasCtx.getImageData(0, 0, canvas.width, canvas.height);
-              for (let i = 0; i < canvas.height; i++) {
-                for (let j = 0; j < canvas.width; j++) {
-                  assertPixel(t, imgData.data.slice(i * canvas.width + j * 4, i * canvas.width + j * 4 + 4), pixelColour);
-                }
-              }
-              resolve();
-            });
-          },
-          close() {
-            assert_unreached("Closed");
-          },
-          abort(err) {
-            assert_unreached("Sink error:" + err);
-          }
-        });
-
-        const videoTrackProcessor = new MediaStreamTrackProcessor(videoTrack);
-        videoTrackProcessor.readable.pipeTo(writableStream);
-
-        t.step(() => {
-          assert_false(videoTrack.muted, "Video track shouldn't be muted after attaching Processing.");
-        });
-      });
-    }, "Tests that creating a Video MediaStreamTrackProcessor works as expected");
-
-    promise_test(async t => {
-      const iAmNotATrack = "notatrack";
-      assert_throws_js(TypeError, () => { new MediaStreamTrackProcessor(iAmNotATrack) });
-    }, "Tests that construction of a MediaStreamTrackProcessor with an invalid track throws.");
-  </script>
-</body>
-
-</html>
diff --git a/third_party/blink/web_tests/wpt_internal/handwriting/handwriting-recognition-interface.https.html b/third_party/blink/web_tests/wpt_internal/handwriting/handwriting-recognition-interface.https.html
new file mode 100644
index 0000000..b20b0e4
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/handwriting/handwriting-recognition-interface.https.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Handwriting Recognition API: Test calls from WebIDL to mojo and back.</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+  import {generateHandwritingPrediction} from './resources/mock-handwriting-recognition-service.js';
+
+  promise_test(async () => {
+    assert_not_equals(navigator.queryHandwritingRecognizerSupport, undefined);
+    assert_not_equals(navigator.queryHandwritingRecognizerSupport, null);
+    assert_not_equals(navigator.createHandwritingRecognizer, undefined);
+    assert_not_equals(navigator.createHandwritingRecognizer, null);
+    assert_not_equals(HandwritingStroke, undefined);
+    assert_not_equals(HandwritingStroke, null);
+  }, 'Handwriting recognition exposed interfaces exist.');
+
+  promise_test(async () => {
+    const stroke = new HandwritingStroke();
+    assert_equals(stroke.getPoints().length, 0);
+    stroke.addPoint({x:123, y:456, t:20});
+    stroke.addPoint({x:31, y:654, t:100});
+    assert_equals(stroke.getPoints().length, 2);
+    assert_equals(stroke.getPoints()[0].x, 123);
+    assert_equals(stroke.getPoints()[0].y, 456);
+    assert_equals(stroke.getPoints()[0].t, 20);
+    assert_equals(stroke.getPoints()[1].x, 31);
+    assert_equals(stroke.getPoints()[1].y, 654);
+    assert_equals(stroke.getPoints()[1].t, 100);
+  }, 'HandwritingStroke works as expected.');
+
+  promise_test(async () => {
+    const stroke = new HandwritingStroke();
+    stroke.addPoint({x:1, y:2, t:3});
+    assert_equals(stroke.getPoints().length, 1);
+    assert_equals(stroke.getPoints()[0].x, 1);
+    assert_equals(stroke.getPoints()[0].y, 2);
+    assert_equals(stroke.getPoints()[0].t, 3);
+    stroke.getPoints()[0].x = 100;
+    stroke.getPoints()[0].y = 200;
+    stroke.getPoints()[0].t = 300;
+    assert_equals(stroke.getPoints()[0].x, 1);
+    assert_equals(stroke.getPoints()[0].y, 2);
+    assert_equals(stroke.getPoints()[0].t, 3);
+  }, 'HandwritingPoint can not be changed after added.');
+
+  promise_test(async () => {
+    const response = await navigator.queryHandwritingRecognizerSupport({
+      languages: ['en'],
+      alternatives: false
+    });
+    assert_equals(Object.keys(response).length, 2);
+    assert_equals(response.languages, true);
+    assert_equals(response.alternatives, true);
+  }, 'queryHandwritingRecognizerSupport works.');
+
+  promise_test(async () => {
+    const response = await navigator.createHandwritingRecognizer({languages: ['en']});
+    assert_not_equals(response, undefined);
+    assert_not_equals(response, null);
+    assert_equals(typeof response.startDrawing, 'function');
+    assert_equals(typeof response.finish, 'function');
+  }, 'createHandwritingRecognizer works.');
+
+  promise_test(async () => {
+    const recognizer = await navigator.createHandwritingRecognizer({languages: ['en']});
+    assert_not_equals(recognizer, undefined);
+    assert_not_equals(recognizer, null);
+
+    const stroke1 = new HandwritingStroke();
+    stroke1.addPoint({x:1, y:2, t:3});
+    stroke1.addPoint({x:4, y:5, t:6});
+    const stroke2 = new HandwritingStroke();
+    stroke2.addPoint({x:7, y:8, t:9});
+    stroke2.addPoint({x:10, y:11, t:12});
+
+    const hints = {
+      recognitionType: "recognition_type_for_test",
+      inputType: "input_type_for_test",
+      textContext: "text_context_for_test",
+      alternatives: 3
+    }
+    const drawing = recognizer.startDrawing(hints);
+    assert_not_equals(drawing, undefined);
+    assert_not_equals(drawing, null);
+    drawing.addStroke(stroke1);
+    drawing.addStroke(stroke2);
+    assert_equals(drawing.getStrokes().length, 2);
+    drawing.removeStroke(stroke1);
+    assert_equals(drawing.getStrokes().length, 1);
+    assert_equals(drawing.getStrokes()[0], stroke2);
+    drawing.clear();
+    assert_equals(drawing.getStrokes().length, 0);
+
+    recognizer.finish();
+  }, 'HandwritingDrawing can add, remove, clear strokes');
+
+  promise_test(async () => {
+    const recognizer = await navigator.createHandwritingRecognizer({languages: ['en']});
+    assert_not_equals(recognizer, undefined);
+    assert_not_equals(recognizer, null);
+
+    const stroke1 = new HandwritingStroke();
+    stroke1.addPoint({x:1, y:2, t:3});
+    stroke1.addPoint({x:4, y:5, t:6});
+    const stroke2 = new HandwritingStroke();
+    stroke2.addPoint({x:7, y:8, t:9});
+    stroke2.addPoint({x:10, y:11, t:12});
+
+    const hints = {
+      recognitionType: "recognition_type_for_test",
+      inputType: "input_type_for_test",
+      textContext: "text_context_for_test",
+      alternatives: 3
+    }
+    const drawing = recognizer.startDrawing(hints);
+    assert_not_equals(drawing, undefined);
+    assert_not_equals(drawing, null);
+    drawing.addStroke(stroke1);
+    drawing.addStroke(stroke2);
+
+    const prediction = await drawing.getPrediction();
+    assert_equals(prediction.length, 1);
+    assert_equals(prediction[0].segmentationResult.length, 0);
+
+    const expected_prediction = generateHandwritingPrediction(drawing.getStrokes(), hints);
+    assert_equals(expected_prediction.length, 1);
+    assert_equals(prediction[0].text, expected_prediction[0].text);
+    assert_equals(expected_prediction[0].segmentationResult.length, 0);
+
+    recognizer.finish();
+  }, 'HandwritingDrawing.getPrediction() works');
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/handwriting/resources/mock-handwriting-recognition-service.js b/third_party/blink/web_tests/wpt_internal/handwriting/resources/mock-handwriting-recognition-service.js
new file mode 100644
index 0000000..e31db76
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/handwriting/resources/mock-handwriting-recognition-service.js
@@ -0,0 +1,96 @@
+import {CreateHandwritingRecognizerResult, HandwritingRecognitionService, HandwritingRecognitionServiceReceiver, HandwritingRecognizerReceiver, HandwritingRecognizerRemote} from '/gen/third_party/blink/public/mojom/handwriting/handwriting.mojom.m.js';
+
+// Generates the prediction result based on strokes and hints.
+// The segmentation result is empty.
+function transformHandwritingMojoStroke(stroke) {
+  return stroke.points.map(point => ({
+    x: Math.round(point.location.x),
+    y: Math.round(point.location.y),
+    t: Math.round(Number(point.t.microseconds) / 1000)}));
+}
+
+function transformHandwritingIDLStroke(stroke) {
+  return stroke.getPoints().map(point => ({
+    x: Math.round(point.x),
+    y: Math.round(point.y),
+    t: Math.round(point.t)}));
+}
+
+// We need to export this function because we will verify whether the prediction
+// result is as expected.
+export function generateHandwritingPrediction(strokes, hints) {
+  const result = { strokes: [] };
+  for (let i = 0; i < strokes.length; i++) {
+    // Check which kind of stroke it is. Mojo Stroke should have a `points`
+    // member and IDL stroke does not.
+    // Note that `strokes[i] instanceof HandwritingStroke` does not work here.
+    if ('points' in strokes[i]) {
+      result.strokes.push(transformHandwritingMojoStroke(strokes[i]));
+    } else {
+      result.strokes.push(transformHandwritingIDLStroke(strokes[i]));
+    }
+  }
+  result.hints = hints;
+  return [{text: JSON.stringify(result), segmentationResult: []}];
+}
+
+class MockHandwritingRecognizer {
+  // In this mock impl, we ignore the `modelConstraint`.
+  constructor(modelConstraint) {}
+
+  bind(request) {
+    this.receiver_ = new HandwritingRecognizerReceiver(this);
+    this.receiver_.$.bindHandle(request.handle);
+  }
+
+  async getPrediction(strokes, hints) {
+    return {prediction: generateHandwritingPrediction(strokes, hints)};
+  }
+}
+
+let mockHandwritingRecognizer =
+    new MockHandwritingRecognizer({languages: ['en']});
+
+class MockHandwritingRecognitionService {
+  constructor() {
+    this.interceptor_ = new MojoInterfaceInterceptor(
+        HandwritingRecognitionService.$interfaceName);
+    this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
+    this.receiver_ = new HandwritingRecognitionServiceReceiver(this);
+
+    this.interceptor_.start();
+  }
+
+  bind(handle) {
+    this.receiver_.$.bindHandle(handle);
+  }
+
+  async createHandwritingRecognizer(modelConstraint) {
+    const handwritingRecognizer = new HandwritingRecognizerRemote();
+    mockHandwritingRecognizer.bind(
+        handwritingRecognizer.$.bindNewPipeAndPassReceiver());
+
+    return {
+      result: CreateHandwritingRecognizerResult.kOk,
+      handwritingRecognizer: handwritingRecognizer,
+    };
+  }
+
+  async queryHandwritingRecognizerSupport(query) {
+    const support = {};
+    // In this mock class, we pretend we support all features.
+    if (query.languages.length !== 0) {
+      support.languages = true;
+    }
+    if (query.alternatives === true) {
+      support.alternatives = true;
+    }
+    if (query.segmentationResult === true) {
+      support.segmentationResult = true;
+    }
+
+    return {result: support};
+  }
+}
+
+let mockHandwritingRecognitionService = new MockHandwritingRecognitionService();
diff --git a/third_party/closure_compiler/externs/bluetooth.js b/third_party/closure_compiler/externs/bluetooth.js
index e2a685be..86e7ba4 100644
--- a/third_party/closure_compiler/externs/bluetooth.js
+++ b/third_party/closure_compiler/externs/bluetooth.js
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2021 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.
 
@@ -11,9 +11,7 @@
 
 /** @fileoverview Externs generated from namespace: bluetooth */
 
-/**
- * @const
- */
+/** @const */
 chrome.bluetooth = {};
 
 /**
@@ -130,13 +128,13 @@
 /**
  * Get a list of Bluetooth devices known to the system, including paired and
  * recently discovered devices.
- * @param {!chrome.bluetooth.BluetoothFilter=} filter Some criteria to filter
- *     the list of returned bluetooth devices. If the filter is not set or set
- *     to <code>{}</code>, returned device list will contain all bluetooth
- *     devices. Right now this is only supported in ChromeOS, for other
- *     platforms, a full list is returned.
- * @param {function(!Array<!chrome.bluetooth.Device>): void=} callback Called when
- *     the search is completed.
+ * @param {?chrome.bluetooth.BluetoothFilter|undefined} filter Some criteria to
+ *     filter the list of returned bluetooth devices. If the filter is not set
+ *     or set to <code>{}</code>, returned device list will contain all
+ *     bluetooth devices. Right now this is only supported in ChromeOS, for
+ *     other platforms, a full list is returned.
+ * @param {function(!Array<!chrome.bluetooth.Device>): void} callback Called
+ *     when the search is completed.
  * @see https://developer.chrome.com/extensions/bluetooth#method-getDevices
  */
 chrome.bluetooth.getDevices = function(filter, callback) {};
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b6a4a40..de3b3e70 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3179,6 +3179,8 @@
   <int value="39" label="Omnibox, Navsuggest"/>
   <int value="40" label="Omnibox Rich Entity, Answer"/>
   <int value="41" label="Omnibox Rich Entity, Image Entity"/>
+  <int value="42" label="Local file search"/>
+  <int value="43" label="Drive file search"/>
 </enum>
 
 <enum name="AppListSearchResultDisplayType">
@@ -7655,11 +7657,16 @@
   <int value="6" label="Not supported"/>
   <int value="7" label="Connection Establishment failed"/>
   <int value="8" label="Authentication failed"/>
-  <int value="9" label="Authentication rejected"/>
-  <int value="10" label="Authentication cancelled"/>
-  <int value="11" label="Authentication timeout"/>
+  <int value="9" label="Pairing rejected"/>
+  <int value="10" label="Pairing cancelled"/>
+  <int value="11" label="Connection timeout"/>
   <int value="12" label="Unknown"/>
   <int value="13" label="BT IO connection failed"/>
+  <int value="14" label="Unknown command"/>
+  <int value="15" label="Peer not connected"/>
+  <int value="16" label="No resources"/>
+  <int value="17" label="Peer disconnected"/>
+  <int value="18" label="Other failures"/>
 </enum>
 
 <enum name="BlueZResultOfPerProfileConnection">
@@ -23905,6 +23912,7 @@
   <int value="836" label="AudioProcessHighPriorityEnabled"/>
   <int value="837" label="SerialAllowAllPortsForUrls"/>
   <int value="838" label="SerialAllowUsbDevicesForUrls"/>
+  <int value="839" label="ForcedLanguages"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -39326,6 +39334,7 @@
   <int value="1" label="Google Apps"/>
   <int value="2" label="Chrome - Other features"/>
   <int value="3" label="Chrome - Reader mode"/>
+  <int value="4" label="Chrome - Read later"/>
 </enum>
 
 <enum name="Inconsistencies">
@@ -39996,6 +40005,11 @@
   <int value="63" label="DELETE_OLD_VERSIONS_TOO_MANY_ATTEMPTS"/>
   <int value="64" label="STORE_DMTOKEN_FAILED"/>
   <int value="65" label="STORE_DMTOKEN_SUCCESS"/>
+  <int value="66" label="DOWNGRADE_CLEANUP_FAILED"/>
+  <int value="67" label="DOWNGRADE_CLEANUP_SUCCESS"/>
+  <int value="68" label="UNDO_DOWNGRADE_CLEANUP_FAILED"/>
+  <int value="69" label="UNDO_DOWNGRADE_CLEANUP_SUCCESS"/>
+  <int value="70" label="DOWNGRADE_CLEANUP_UNKNOWN_OPERATION"/>
 </enum>
 
 <enum name="InstanceIDResult">
diff --git a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
index e55e586cc..d1d7eb0 100644
--- a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
@@ -21,6 +21,13 @@
 
 <histograms>
 
+<variants name="NavigationThrottleEvents">
+  <variant name="WillFailRequest"/>
+  <variant name="WillProcessResponse"/>
+  <variant name="WillRedirectRequest"/>
+  <variant name="WillStartRequest"/>
+</variants>
+
 <histogram name="BackForwardCache.AllSites.EvictedAfterDocumentRestoredReason"
     enum="BackForwardCacheEvictedAfterDocumentRestoredReason"
     expires_after="2021-10-01">
@@ -1027,6 +1034,28 @@
   </summary>
 </histogram>
 
+<histogram name="Navigation.ThrottleDeferTime.{Event}" units="ms"
+    expires_after="2022-03-11">
+  <owner>cduvall@chromium.org</owner>
+  <owner>jam@chromium.org</owner>
+  <summary>
+    Measures time a navigation throttle was deferred when on {Event}. Logged
+    every time a throttle defers a navigation.
+  </summary>
+  <token key="Event" variants="NavigationThrottleEvents"/>
+</histogram>
+
+<histogram name="Navigation.ThrottleExecutionTime.{Event}" units="ms"
+    expires_after="2022-03-11">
+  <owner>cduvall@chromium.org</owner>
+  <owner>jam@chromium.org</owner>
+  <summary>
+    Measures time a navigation throttle took to execute {Event}. Logged every
+    time a throttle runs.
+  </summary>
+  <token key="Event" variants="NavigationThrottleEvents"/>
+</histogram>
+
 <histogram name="Navigation.TimeToReadyToCommit2" units="ms"
     expires_after="M85">
   <owner>clamy@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 62de6dd..4db6811 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -11324,6 +11324,9 @@
 
 <histogram name="PrefService.PersistentLogRecallProtobufs"
     enum="PersistedLogsLogReadStatus" expires_after="M77">
+  <obsolete>
+    Expired in M77, later removed.
+  </obsolete>
   <owner>holte@chromium.org</owner>
   <summary>The status when loading PersistedLogs from Prefs.</summary>
 </histogram>
@@ -18036,6 +18039,9 @@
 
 <histogram name="WebFont.FontDisplayValue" enum="FontDisplayValue"
     expires_after="2021-03-21">
+  <obsolete>
+    Removed 2021-03
+  </obsolete>
   <owner>kenjibaheux@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 93bed0c..2a23ec3 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -333,10 +333,16 @@
     _load_library_perf_tests(),
     _performance_browser_tests(210),
 ])
-_MAC_M1_MINI_2020_BENCHMARK_CONFIGS = PerfSuite([
-    'loading.desktop',
-]).Abridge([
-    'loading.desktop',
+_MAC_M1_MINI_2020_BENCHMARK_CONFIGS = PerfSuite(
+    OFFICIAL_BENCHMARK_CONFIGS).Remove([
+        'blink_perf.display_locking',
+        'v8.runtime_stats.top_25',
+    ])
+_MAC_M1_MINI_2020_EXECUTABLE_CONFIGS = frozenset([
+    _base_perftests(300),
+    _dawn_perf_tests(330),
+    _performance_browser_tests(190),
+    _views_perftests(),
 ])
 
 _WIN_10_BENCHMARK_CONFIGS = PerfSuite(OFFICIAL_BENCHMARK_CONFIGS).Remove([
@@ -496,8 +502,13 @@
     26,
     'mac',
     executables=_MAC_LOW_END_EXECUTABLE_CONFIGS)
-MAC_M1_MINI_2020 = PerfPlatform('mac-m1_mini_2020-perf', 'Mac M1 Mini 2020',
-                                _MAC_M1_MINI_2020_BENCHMARK_CONFIGS, 2, 'mac')
+MAC_M1_MINI_2020 = PerfPlatform(
+    'mac-m1_mini_2020-perf',
+    'Mac M1 Mini 2020',
+    _MAC_M1_MINI_2020_BENCHMARK_CONFIGS,
+    8,
+    'mac',
+    executables=_MAC_M1_MINI_2020_EXECUTABLE_CONFIGS)
 
 # Win
 WIN_10_LOW_END = PerfPlatform(
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 06602ac6..0704046 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "357871e88af417fca6bd842ef96284035c69bcae",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/066c7f00bf7af468db2a2f52da89b8963248c426/trace_processor_shell.exe"
+            "hash": "6acd2fb9651bcbb5cb59963637b5d47848fc1476",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/4973272defb4081572496c831ca3e55c6d12c99a/trace_processor_shell.exe"
         },
         "mac": {
             "hash": "0a851874f1a85eddc033b36017a0945bd0ca2627",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/a4edfc0db10b623d6fdf3281b358bc131bade994/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/4973272defb4081572496c831ca3e55c6d12c99a/trace_processor_shell"
         },
         "linux": {
-            "hash": "7a79f317edcbc5e07fd74df8c2548d75f0fe4efa",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/4973272defb4081572496c831ca3e55c6d12c99a/trace_processor_shell"
+            "hash": "9f560ce138859ba0808168b69a9376a91bc5bdeb",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/0f1ec9f510ae0cc8f800d08e8205017b6a8b8f3b/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
index ad83860..4ce5769 100644
--- a/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
+++ b/tools/perf/core/shard_maps/mac-m1_mini_2020-perf_map.json
@@ -1,27 +1,251 @@
 {
     "0": {
         "benchmarks": {
-            "loading.desktop": {
-                "end": 5,
-                "abridged": true
+            "blink_perf.accessibility": {
+                "abridged": false
+            },
+            "blink_perf.bindings": {
+                "abridged": false
+            },
+            "blink_perf.css": {
+                "abridged": false
+            },
+            "blink_perf.dom": {
+                "abridged": false
+            },
+            "blink_perf.events": {
+                "abridged": false
+            },
+            "blink_perf.image_decoder": {
+                "end": 6,
+                "abridged": false
+            }
+        },
+        "executables": {
+            "base_perftests": {
+                "path": "base_perftests",
+                "arguments": [
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0"
+                ]
             }
         }
     },
     "1": {
         "benchmarks": {
+            "blink_perf.image_decoder": {
+                "begin": 6,
+                "abridged": false
+            },
+            "blink_perf.layout": {
+                "abridged": false
+            },
+            "blink_perf.owp_storage": {
+                "abridged": false
+            },
+            "blink_perf.paint": {
+                "abridged": false
+            },
+            "blink_perf.parser": {
+                "abridged": false
+            },
+            "blink_perf.shadow_dom": {
+                "end": 3,
+                "abridged": false
+            }
+        }
+    },
+    "2": {
+        "benchmarks": {
+            "blink_perf.shadow_dom": {
+                "begin": 3,
+                "abridged": false
+            },
+            "blink_perf.svg": {
+                "abridged": false
+            },
+            "blink_perf.webaudio": {
+                "abridged": false
+            },
+            "blink_perf.webgl": {
+                "abridged": false
+            },
+            "blink_perf.webgl_fast_call": {
+                "abridged": false
+            },
+            "blink_perf.webgpu": {
+                "abridged": false
+            },
+            "blink_perf.webgpu_fast_call": {
+                "abridged": false
+            },
+            "desktop_ui": {
+                "abridged": false
+            },
+            "dromaeo": {
+                "abridged": false
+            },
+            "dummy_benchmark.noisy_benchmark_1": {
+                "abridged": false
+            },
+            "dummy_benchmark.stable_benchmark_1": {
+                "abridged": false
+            },
+            "jetstream": {
+                "abridged": false
+            },
+            "jetstream2": {
+                "abridged": false
+            },
+            "kraken": {
+                "abridged": false
+            },
             "loading.desktop": {
-                "begin": 5,
-                "abridged": true
+                "end": 17,
+                "abridged": false
+            }
+        },
+        "executables": {
+            "dawn_perf_tests": {
+                "path": "dawn_perf_tests",
+                "arguments": [
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0"
+                ]
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "loading.desktop": {
+                "begin": 17,
+                "abridged": false
+            },
+            "media.desktop": {
+                "abridged": false
+            },
+            "memory.desktop": {
+                "abridged": false
+            },
+            "octane": {
+                "abridged": false
+            }
+        },
+        "executables": {
+            "performance_browser_tests": {
+                "path": "browser_tests",
+                "arguments": [
+                    "--full-performance-run",
+                    "--test-launcher-jobs=1",
+                    "--test-launcher-retry-limit=0",
+                    "--ui-test-action-timeout=60000",
+                    "--ui-test-action-max-timeout=60000",
+                    "--test-launcher-timeout=60000",
+                    "--gtest_filter=*/TabCapturePerformanceTest.*:*/CastV2PerformanceTest.*"
+                ]
+            }
+        }
+    },
+    "4": {
+        "benchmarks": {
+            "power.desktop": {
+                "abridged": false
+            },
+            "rasterize_and_record_micro.top_25": {
+                "abridged": false
+            },
+            "rendering.desktop": {
+                "end": 110,
+                "abridged": false
+            }
+        }
+    },
+    "5": {
+        "benchmarks": {
+            "rendering.desktop": {
+                "begin": 110,
+                "end": 260,
+                "abridged": false
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
+            "rendering.desktop": {
+                "begin": 260,
+                "abridged": false
+            },
+            "speedometer": {
+                "abridged": false
+            },
+            "speedometer-future": {
+                "abridged": false
+            },
+            "speedometer2": {
+                "abridged": false
+            },
+            "speedometer2-future": {
+                "abridged": false
+            },
+            "speedometer2-pcscan": {
+                "abridged": false
+            },
+            "system_health.common_desktop": {
+                "abridged": false
+            },
+            "system_health.memory_desktop": {
+                "end": 14,
+                "abridged": false
+            }
+        }
+    },
+    "7": {
+        "benchmarks": {
+            "system_health.memory_desktop": {
+                "begin": 14,
+                "abridged": false
+            },
+            "system_health.pcscan": {
+                "abridged": false
+            },
+            "tab_switching.typical_25": {
+                "abridged": false
+            },
+            "tracing.tracing_with_background_memory_infra": {
+                "abridged": false
+            },
+            "v8.browsing_desktop": {
+                "abridged": false
+            },
+            "v8.browsing_desktop-future": {
+                "abridged": false
+            },
+            "webrtc": {
+                "abridged": false
+            }
+        },
+        "executables": {
+            "views_perftests": {
+                "path": "views_perftests",
+                "arguments": [
+                    "--xvfb"
+                ]
             }
         }
     },
     "extra_infos": {
-        "num_stories": 10,
-        "predicted_min_shard_time": 170.0,
-        "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 224.0,
-        "predicted_max_shard_index": 1,
-        "shard #0": 170.0,
-        "shard #1": 224.0
+        "num_stories": 1109,
+        "predicted_min_shard_time": 1497.0,
+        "predicted_min_shard_index": 7,
+        "predicted_max_shard_time": 1616.0,
+        "predicted_max_shard_index": 3,
+        "shard #0": 1520.0,
+        "shard #1": 1520,
+        "shard #2": 1524.0,
+        "shard #3": 1616.0,
+        "shard #4": 1500,
+        "shard #5": 1500,
+        "shard #6": 1500,
+        "shard #7": 1497.0
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/mac-m1_mini_2020-perf_timing.json b/tools/perf/core/shard_maps/timing_data/mac-m1_mini_2020-perf_timing.json
index b9f44c8..ee72798 100644
--- a/tools/perf/core/shard_maps/timing_data/mac-m1_mini_2020-perf_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/mac-m1_mini_2020-perf_timing.json
@@ -1,6 +1,6 @@
 [
     {
-        "duration": "28.0",
+        "duration": "29.0",
         "name": "loading.desktop/AirBnB_warm"
     },
     {
@@ -20,7 +20,7 @@
         "name": "loading.desktop/Naver_cold"
     },
     {
-        "duration": "15.0",
+        "duration": "14.0",
         "name": "loading.desktop/Orange_cold"
     },
     {
@@ -28,7 +28,7 @@
         "name": "loading.desktop/Orange_warm"
     },
     {
-        "duration": "31.0",
+        "duration": "33.0",
         "name": "loading.desktop/Taobao_warm"
     },
     {
@@ -36,7 +36,23 @@
         "name": "loading.desktop/TheOnion_cold"
     },
     {
-        "duration": "16.0",
+        "duration": "17.0",
         "name": "loading.desktop/ru.wikipedia_warm"
+    },
+    {
+        "duration": "300.0",
+        "name": "base_perftests/_gtest_"
+    },
+    {
+        "duration": "190.0",
+        "name": "performance_browser_tests/_gtest_"
+    },
+    {
+        "duration": "7.0",
+        "name": "views_perftests/_gtest_"
+    },
+    {
+        "duration": "330.0",
+        "name": "dawn_perf_tests/_gtest_"
     }
 ]
\ No newline at end of file
diff --git a/ui/accessibility/ax_event.cc b/ui/accessibility/ax_event.cc
index ecb80d9..0b346140 100644
--- a/ui/accessibility/ax_event.cc
+++ b/ui/accessibility/ax_event.cc
@@ -14,11 +14,13 @@
 AXEvent::AXEvent(AXNodeData::AXID id,
                  ax::mojom::Event event_type,
                  ax::mojom::EventFrom event_from,
+                 ax::mojom::Action event_from_action,
                  const std::vector<AXEventIntent>& event_intents,
                  int action_request_id)
     : id(id),
       event_type(event_type),
       event_from(event_from),
+      event_from_action(event_from_action),
       event_intents(event_intents),
       action_request_id(action_request_id) {}
 
@@ -35,6 +37,9 @@
   result += " on node id=" + base::NumberToString(id);
   if (event_from != ax::mojom::EventFrom::kNone)
     result += std::string(" from ") + ui::ToString(event_from);
+  if (event_from_action != ax::mojom::Action::kNone)
+    result += std::string(" from accessibility action ") +
+              ui::ToString(event_from_action);
   if (!event_intents.empty()) {
     result += " caused by [ ";
     for (const AXEventIntent& intent : event_intents) {
diff --git a/ui/accessibility/ax_event.h b/ui/accessibility/ax_event.h
index 86869b6..6eb3c10 100644
--- a/ui/accessibility/ax_event.h
+++ b/ui/accessibility/ax_event.h
@@ -20,6 +20,7 @@
   AXEvent(AXNodeData::AXID id,
           ax::mojom::Event event_type,
           ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone,
+          ax::mojom::Action event_from_action = ax::mojom::Action::kNone,
           const std::vector<AXEventIntent>& event_intents = {},
           int action_request_id = -1);
   virtual ~AXEvent();
@@ -35,6 +36,12 @@
   // The source of the event.
   ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
 
+  // The accessibility action that caused the event. The difference between
+  // event_from_action and event_intents is that event_from_action refers to
+  // an action from an accessibility API call, while event_intents refers to
+  // user actions.
+  ax::mojom::Action event_from_action = ax::mojom::Action::kNone;
+
   // Describes what caused an accessibility event to be raised. For example, in
   // the case of a selection changed event, the selection could have been
   // extended to the beginning of the previous word, or it could have been moved
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 23294b6..ebc55f7 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -75,8 +75,12 @@
 AXEventGenerator::EventParams::EventParams(
     const Event event,
     const ax::mojom::EventFrom event_from,
+    const ax::mojom::Action event_from_action,
     const std::vector<AXEventIntent>& event_intents)
-    : event(event), event_from(event_from), event_intents(event_intents) {}
+    : event(event),
+      event_from(event_from),
+      event_from_action(event_from_action),
+      event_intents(event_intents) {}
 
 AXEventGenerator::EventParams::EventParams(const EventParams& other) = default;
 
@@ -261,7 +265,7 @@
 
   std::set<EventParams>& node_events = tree_events_[node];
   node_events.emplace(event, ax::mojom::EventFrom::kNone,
-                      tree_->event_intents());
+                      ax::mojom::Action::kNone, tree_->event_intents());
 }
 
 void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
diff --git a/ui/accessibility/ax_event_generator.h b/ui/accessibility/ax_event_generator.h
index 339ab61..96dbf98 100644
--- a/ui/accessibility/ax_event_generator.h
+++ b/ui/accessibility/ax_event_generator.h
@@ -128,6 +128,7 @@
     explicit EventParams(Event event);
     EventParams(Event event,
                 ax::mojom::EventFrom event_from,
+                ax::mojom::Action event_from_action,
                 const std::vector<AXEventIntent>& event_intents);
     EventParams(const EventParams& other);
     ~EventParams();
@@ -138,6 +139,7 @@
 
     Event event;
     ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
+    ax::mojom::Action event_from_action;
     std::vector<AXEventIntent> event_intents;
   };
 
diff --git a/ui/accessibility/ax_event_generator_unittest.cc b/ui/accessibility/ax_event_generator_unittest.cc
index 491578e..e612489 100644
--- a/ui/accessibility/ax_event_generator_unittest.cc
+++ b/ui/accessibility/ax_event_generator_unittest.cc
@@ -104,11 +104,14 @@
   // Node3 contains IGNORED_CHANGED, SUBTREE_CREATED, NAME_CHANGED.
   std::set<AXEventGenerator::EventParams> node3_events;
   node3_events.emplace(AXEventGenerator::Event::IGNORED_CHANGED,
-                       ax::mojom::EventFrom::kNone, tree.event_intents());
+                       ax::mojom::EventFrom::kNone, ax::mojom::Action::kNone,
+                       tree.event_intents());
   node3_events.emplace(AXEventGenerator::Event::SUBTREE_CREATED,
-                       ax::mojom::EventFrom::kNone, tree.event_intents());
+                       ax::mojom::EventFrom::kNone, ax::mojom::Action::kNone,
+                       tree.event_intents());
   node3_events.emplace(AXEventGenerator::Event::NAME_CHANGED,
-                       ax::mojom::EventFrom::kNone, tree.event_intents());
+                       ax::mojom::EventFrom::kNone, ax::mojom::Action::kNone,
+                       tree.event_intents());
   // Node4 contains no event.
   std::set<AXEventGenerator::EventParams> node4_events;
   // Node5 contains no event.
@@ -118,7 +121,8 @@
   // Node7 contains IGNORED_CHANGED.
   std::set<AXEventGenerator::EventParams> node7_events;
   node7_events.emplace(AXEventGenerator::Event::IGNORED_CHANGED,
-                       ax::mojom::EventFrom::kNone, tree.event_intents());
+                       ax::mojom::EventFrom::kNone, ax::mojom::Action::kNone,
+                       tree.event_intents());
   // Node8 contains no event.
   std::set<AXEventGenerator::EventParams> node8_events;
   // Node9 contains no event.
diff --git a/ui/accessibility/ax_param_traits_macros.h b/ui/accessibility/ax_param_traits_macros.h
index c653f646..df7ce0b9 100644
--- a/ui/accessibility/ax_param_traits_macros.h
+++ b/ui/accessibility/ax_param_traits_macros.h
@@ -44,6 +44,7 @@
                           ax::mojom::TextBoundary::kMaxValue)
 IPC_ENUM_TRAITS_MAX_VALUE(ax::mojom::MoveDirection,
                           ax::mojom::MoveDirection::kMaxValue)
+IPC_ENUM_TRAITS_MAX_VALUE(ax::mojom::Action, ax::mojom::Action::kMaxValue)
 
 IPC_STRUCT_TRAITS_BEGIN(ui::AXRelativeBounds)
   IPC_STRUCT_TRAITS_MEMBER(offset_container_id)
@@ -55,6 +56,7 @@
   IPC_STRUCT_TRAITS_MEMBER(event_type)
   IPC_STRUCT_TRAITS_MEMBER(id)
   IPC_STRUCT_TRAITS_MEMBER(event_from)
+  IPC_STRUCT_TRAITS_MEMBER(event_from_action)
   IPC_STRUCT_TRAITS_MEMBER(event_intents)
   IPC_STRUCT_TRAITS_MEMBER(action_request_id)
 IPC_STRUCT_TRAITS_END()
@@ -109,6 +111,7 @@
   IPC_STRUCT_TRAITS_MEMBER(root_id)
   IPC_STRUCT_TRAITS_MEMBER(nodes)
   IPC_STRUCT_TRAITS_MEMBER(event_from)
+  IPC_STRUCT_TRAITS_MEMBER(event_from_action)
   IPC_STRUCT_TRAITS_MEMBER(event_intents)
 IPC_STRUCT_TRAITS_END()
 
diff --git a/ui/accessibility/ax_tree_update.cc b/ui/accessibility/ax_tree_update.cc
index 6876c310..b47132c1c 100644
--- a/ui/accessibility/ax_tree_update.cc
+++ b/ui/accessibility/ax_tree_update.cc
@@ -34,6 +34,10 @@
 
   if (event_from != ax::mojom::EventFrom::kNone)
     result += "event_from=" + std::string(ui::ToString(event_from)) + "\n";
+  if (event_from_action != ax::mojom::Action::kNone)
+    result +=
+        "event_from_action=" + std::string(ui::ToString(event_from_action)) +
+        "\n";
 
   if (!event_intents.empty()) {
     result += "event_intents=[\n";
diff --git a/ui/accessibility/ax_tree_update.h b/ui/accessibility/ax_tree_update.h
index d318d81..3c69827d 100644
--- a/ui/accessibility/ax_tree_update.h
+++ b/ui/accessibility/ax_tree_update.h
@@ -77,6 +77,9 @@
   // The source of the event which generated this tree update.
   ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
 
+  // The accessibility action that caused this tree update.
+  ax::mojom::Action event_from_action = ax::mojom::Action::kNone;
+
   // The event intents associated with this tree update.
   std::vector<AXEventIntent> event_intents;
 
diff --git a/ui/accessibility/mojom/ax_event.mojom b/ui/accessibility/mojom/ax_event.mojom
index 857af93..5a42cf08 100644
--- a/ui/accessibility/mojom/ax_event.mojom
+++ b/ui/accessibility/mojom/ax_event.mojom
@@ -12,6 +12,7 @@
   Event event_type;
   int32 id;
   EventFrom event_from;
+  Action event_from_action;
   array<EventIntent> event_intents;
   int32 action_request_id;
 };
diff --git a/ui/accessibility/mojom/ax_event_mojom_traits.cc b/ui/accessibility/mojom/ax_event_mojom_traits.cc
index 1b927ea..f364ccc 100644
--- a/ui/accessibility/mojom/ax_event_mojom_traits.cc
+++ b/ui/accessibility/mojom/ax_event_mojom_traits.cc
@@ -13,6 +13,7 @@
   out->event_type = data.event_type();
   out->id = data.id();
   out->event_from = data.event_from();
+  out->event_from_action = data.event_from_action();
   out->action_request_id = data.action_request_id();
   return data.ReadEventIntents(&out->event_intents);
 }
diff --git a/ui/accessibility/mojom/ax_event_mojom_traits.h b/ui/accessibility/mojom/ax_event_mojom_traits.h
index c192234..e1a900e 100644
--- a/ui/accessibility/mojom/ax_event_mojom_traits.h
+++ b/ui/accessibility/mojom/ax_event_mojom_traits.h
@@ -24,6 +24,9 @@
   static ax::mojom::EventFrom event_from(const ui::AXEvent& p) {
     return p.event_from;
   }
+  static ax::mojom::Action event_from_action(const ui::AXEvent& p) {
+    return p.event_from_action;
+  }
   static std::vector<ui::AXEventIntent> event_intents(const ui::AXEvent& p) {
     return p.event_intents;
   }
diff --git a/ui/accessibility/mojom/ax_event_mojom_traits_unittest.cc b/ui/accessibility/mojom/ax_event_mojom_traits_unittest.cc
index 21fc34a15..edd0a8b 100644
--- a/ui/accessibility/mojom/ax_event_mojom_traits_unittest.cc
+++ b/ui/accessibility/mojom/ax_event_mojom_traits_unittest.cc
@@ -22,6 +22,7 @@
   input.event_type = ax::mojom::Event::kTextChanged;
   input.id = 111;
   input.event_from = ax::mojom::EventFrom::kUser;
+  input.event_from_action = ax::mojom::Action::kDoDefault;
   ui::AXEventIntent editing_intent;
   editing_intent.command = ax::mojom::Command::kDelete;
   editing_intent.input_event_type =
@@ -40,6 +41,7 @@
   EXPECT_EQ(ax::mojom::Event::kTextChanged, output.event_type);
   EXPECT_EQ(111, output.id);
   EXPECT_EQ(ax::mojom::EventFrom::kUser, output.event_from);
+  EXPECT_EQ(ax::mojom::Action::kDoDefault, output.event_from_action);
   EXPECT_THAT(output.event_intents, testing::ContainerEq(event_intents));
   EXPECT_EQ(222, output.action_request_id);
 }
diff --git a/ui/accessibility/mojom/ax_tree_update.mojom b/ui/accessibility/mojom/ax_tree_update.mojom
index 957edd5..493f56f 100644
--- a/ui/accessibility/mojom/ax_tree_update.mojom
+++ b/ui/accessibility/mojom/ax_tree_update.mojom
@@ -17,5 +17,6 @@
   int32 root_id;
   array<AXNodeData> nodes;
   ax.mojom.EventFrom event_from;
+  ax.mojom.Action event_from_action;
   array<EventIntent> event_intents;
 };
diff --git a/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc b/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
index e429ce978d..6dcfe1d 100644
--- a/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
+++ b/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
@@ -18,6 +18,7 @@
   if (!data.ReadNodes(&out->nodes))
     return false;
   out->event_from = data.event_from();
+  out->event_from_action = data.event_from_action();
   return data.ReadEventIntents(&out->event_intents);
 }
 
diff --git a/ui/accessibility/mojom/ax_tree_update_mojom_traits.h b/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
index de0591f..8077a0d5 100644
--- a/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
+++ b/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
@@ -33,6 +33,9 @@
   static ax::mojom::EventFrom event_from(const ui::AXTreeUpdate& p) {
     return p.event_from;
   }
+  static ax::mojom::Action event_from_action(const ui::AXTreeUpdate& p) {
+    return p.event_from_action;
+  }
   static const std::vector<ui::AXEventIntent>& event_intents(
       const ui::AXTreeUpdate& p) {
     return p.event_intents;
diff --git a/ui/accessibility/mojom/ax_tree_update_mojom_traits_unittest.cc b/ui/accessibility/mojom/ax_tree_update_mojom_traits_unittest.cc
index 623bc75..cb5d423 100644
--- a/ui/accessibility/mojom/ax_tree_update_mojom_traits_unittest.cc
+++ b/ui/accessibility/mojom/ax_tree_update_mojom_traits_unittest.cc
@@ -21,6 +21,7 @@
   input.nodes[0].role = ax::mojom::Role::kButton;
   input.nodes[1].id = 4;
   input.event_from = ax::mojom::EventFrom::kUser;
+  input.event_from_action = ax::mojom::Action::kDoDefault;
   EXPECT_TRUE(SerializeAndDeserialize<ax::mojom::AXTreeUpdate>(input, output));
   EXPECT_EQ(true, output.has_tree_data);
   EXPECT_EQ(1, output.tree_data.focus_id);
@@ -30,4 +31,5 @@
   EXPECT_EQ(ax::mojom::Role::kButton, output.nodes[0].role);
   EXPECT_EQ(4, output.nodes[1].id);
   EXPECT_EQ(ax::mojom::EventFrom::kUser, output.event_from);
+  EXPECT_EQ(ax::mojom::Action::kDoDefault, output.event_from_action);
 }
diff --git a/ui/android/java/src/org/chromium/ui/widget/ViewLookupCachingFrameLayout.java b/ui/android/java/src/org/chromium/ui/widget/ViewLookupCachingFrameLayout.java
index 56ebe87..cf8f119 100644
--- a/ui/android/java/src/org/chromium/ui/widget/ViewLookupCachingFrameLayout.java
+++ b/ui/android/java/src/org/chromium/ui/widget/ViewLookupCachingFrameLayout.java
@@ -95,7 +95,7 @@
         View view = null;
         if (ref != null) view = ref.get();
         if (view == null) view = findViewById(id);
-        if (BuildConfig.DCHECK_IS_ON) {
+        if (BuildConfig.ENABLE_ASSERTS) {
             assert view == findViewById(id) : "View caching logic is broken!";
             assert ref == null
                     || ref.get() != null : "Cache held reference to garbage collected view!";
diff --git a/ui/compositor/test/in_process_context_factory.cc b/ui/compositor/test/in_process_context_factory.cc
index ea06f14..7b4fa82 100644
--- a/ui/compositor/test/in_process_context_factory.cc
+++ b/ui/compositor/test/in_process_context_factory.cc
@@ -187,6 +187,7 @@
   void UpdateRefreshRate(float refresh_rate) override {}
   void SetSupportedRefreshRates(
       const std::vector<float>& refresh_rates) override {}
+  void PreserveChildSurfaceControls() override {}
 #endif
 
   void SetDelegatedInkPointRenderer(
diff --git a/ui/gfx/android/android_surface_control_compat.cc b/ui/gfx/android/android_surface_control_compat.cc
index 23f8e09..fea4bd26 100644
--- a/ui/gfx/android/android_surface_control_compat.cc
+++ b/ui/gfx/android/android_surface_control_compat.cc
@@ -85,7 +85,9 @@
              ASurfaceControl* surface_control,
              float frameRate,
              int8_t compatibility);
-
+using pASurfaceTransaction_reparent = void (*)(ASurfaceTransaction*,
+                                               ASurfaceControl* surface_control,
+                                               ASurfaceControl* new_parent);
 // ASurfaceTransactionStats
 using pASurfaceTransactionStats_getPresentFenceFd =
     int (*)(ASurfaceTransactionStats* stats);
@@ -145,6 +147,7 @@
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_delete);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_apply);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setOnComplete);
+    LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_reparent);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setVisibility);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setZOrder);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setBuffer);
@@ -176,6 +179,7 @@
   pASurfaceTransaction_delete ASurfaceTransaction_deleteFn;
   pASurfaceTransaction_apply ASurfaceTransaction_applyFn;
   pASurfaceTransaction_setOnComplete ASurfaceTransaction_setOnCompleteFn;
+  pASurfaceTransaction_reparent ASurfaceTransaction_reparentFn;
   pASurfaceTransaction_setVisibility ASurfaceTransaction_setVisibilityFn;
   pASurfaceTransaction_setZOrder ASurfaceTransaction_setZOrderFn;
   pASurfaceTransaction_setBuffer ASurfaceTransaction_setBufferFn;
@@ -491,6 +495,13 @@
       ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
 }
 
+void SurfaceControl::Transaction::SetParent(const Surface& surface,
+                                            Surface* new_parent) {
+  SurfaceControlMethods::Get().ASurfaceTransaction_reparentFn(
+      transaction_, surface.surface(),
+      new_parent ? new_parent->surface() : nullptr);
+}
+
 void SurfaceControl::Transaction::SetOnCompleteCb(
     OnCompleteCb cb,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
diff --git a/ui/gfx/android/android_surface_control_compat.h b/ui/gfx/android/android_surface_control_compat.h
index 1f34f53..4dc22573 100644
--- a/ui/gfx/android/android_surface_control_compat.h
+++ b/ui/gfx/android/android_surface_control_compat.h
@@ -123,6 +123,7 @@
     void SetColorSpace(const Surface& surface,
                        const gfx::ColorSpace& color_space);
     void SetFrameRate(const Surface& surface, float frame_rate);
+    void SetParent(const Surface& surface, Surface* new_parent);
 
     // Sets the callback which will be dispatched when the transaction is acked
     // by the framework.
diff --git a/ui/gl/gl_surface.h b/ui/gl/gl_surface.h
index 8e041252..1f8c3d8 100644
--- a/ui/gl/gl_surface.h
+++ b/ui/gl/gl_surface.h
@@ -107,6 +107,10 @@
   // Get the underlying platform specific surface "handle".
   virtual void* GetHandle() = 0;
 
+  // Android SurfaceControl specific, notifies that we should not detach child
+  // surface controls during destruction.
+  virtual void PreserveChildSurfaceControls() {}
+
   // Returns whether or not the surface supports SwapBuffersWithBounds
   virtual bool SupportsSwapBuffersWithBounds();
 
diff --git a/ui/gl/gl_surface_egl_surface_control.cc b/ui/gl/gl_surface_egl_surface_control.cc
index b0e7c0f..66a7c37 100644
--- a/ui/gl/gl_surface_egl_surface_control.cc
+++ b/ui/gl/gl_surface_egl_surface_control.cc
@@ -113,7 +113,24 @@
   weak_factory_.InvalidateWeakPtrs();
 }
 
+void GLSurfaceEGLSurfaceControl::PreserveChildSurfaceControls() {
+  TRACE_EVENT_INSTANT0(
+      "gpu", "GLSurfaceEGLSurfaceControl::PreserveChildSurfaceControls",
+      TRACE_EVENT_SCOPE_THREAD);
+  preserve_children_ = true;
+}
+
 void GLSurfaceEGLSurfaceControl::Destroy() {
+  TRACE_EVENT0("gpu", "GLSurfaceEGLSurfaceControl::Destroy");
+  // Detach all child layers to prevent leaking unless browser asked us not too.
+  if (!preserve_children_) {
+    gfx::SurfaceControl::Transaction transaction;
+    for (auto& surface : surface_list_) {
+      transaction.SetParent(*surface.surface, nullptr);
+    }
+    transaction.Apply();
+  }
+
   pending_transaction_.reset();
   surface_list_.clear();
   root_surface_.reset();
diff --git a/ui/gl/gl_surface_egl_surface_control.h b/ui/gl/gl_surface_egl_surface_control.h
index 57e63418..ba24b4c 100644
--- a/ui/gl/gl_surface_egl_surface_control.h
+++ b/ui/gl/gl_surface_egl_surface_control.h
@@ -55,6 +55,7 @@
                             std::unique_ptr<gfx::GpuFence> gpu_fence) override;
   bool IsSurfaceless() const override;
   void* GetHandle() override;
+  void PreserveChildSurfaceControls() override;
 
   // Sync versions of frame update, should never be used.
   gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
@@ -251,6 +252,8 @@
 
   TransactionAckTimeoutManager transaction_ack_timeout_manager_;
 
+  bool preserve_children_ = false;
+
   scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
   base::WeakPtrFactory<GLSurfaceEGLSurfaceControl> weak_factory_{this};
 };
diff --git a/ui/views/corewm/test/tooltip_aura_test_api.cc b/ui/views/corewm/test/tooltip_aura_test_api.cc
index 0fa8aee..5981f69f 100644
--- a/ui/views/corewm/test/tooltip_aura_test_api.cc
+++ b/ui/views/corewm/test/tooltip_aura_test_api.cc
@@ -5,6 +5,7 @@
 #include "ui/views/corewm/test/tooltip_aura_test_api.h"
 
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/render_text.h"
 #include "ui/views/corewm/tooltip_aura.h"
 
@@ -20,6 +21,12 @@
   return tooltip_aura_->GetAccessibleNodeDataForTest(node_data);
 }
 
+gfx::Rect TooltipAuraTestApi::GetTooltipBounds(
+    const gfx::Size& tooltip_size,
+    const TooltipPosition& position) {
+  return tooltip_aura_->GetTooltipBounds(tooltip_size, position);
+}
+
 }  // namespace test
 }  // namespace corewm
 }  // namespace views
diff --git a/ui/views/corewm/test/tooltip_aura_test_api.h b/ui/views/corewm/test/tooltip_aura_test_api.h
index a96a871..cd9327a 100644
--- a/ui/views/corewm/test/tooltip_aura_test_api.h
+++ b/ui/views/corewm/test/tooltip_aura_test_api.h
@@ -10,7 +10,9 @@
 #include "base/macros.h"
 
 namespace gfx {
+class Rect;
 class RenderText;
+class Size;
 }
 
 namespace ui {
@@ -20,6 +22,7 @@
 namespace views {
 namespace corewm {
 class TooltipAura;
+struct TooltipPosition;
 
 namespace test {
 
@@ -32,6 +35,9 @@
 
   void GetAccessibleNodeData(ui::AXNodeData* node_data);
 
+  gfx::Rect GetTooltipBounds(const gfx::Size& tooltip_size,
+                             const TooltipPosition& position);
+
  private:
   TooltipAura* tooltip_aura_;
 
diff --git a/ui/views/corewm/tooltip.h b/ui/views/corewm/tooltip.h
index 0a197b1..a33ddc8 100644
--- a/ui/views/corewm/tooltip.h
+++ b/ui/views/corewm/tooltip.h
@@ -7,19 +7,33 @@
 
 #include <string>
 
+#include "ui/gfx/geometry/point.h"
 #include "ui/views/views_export.h"
 
 namespace aura {
 class Window;
 }
 
-namespace gfx {
-class Point;
-}
-
 namespace views {
 namespace corewm {
 
+enum class TooltipPositionBehavior {
+  // A centered tooltip will have its horizontal center aligned with the anchor
+  // point x value. The top of the tooltip will be aligned with the anchor point
+  // y value.
+  kCentered,
+  // A tooltip positioned relatively to the cursor will have its top-left corner
+  // aligned with the anchor point. It will have an additional offset the size
+  // of the cursor, resulting in the tooltip being positioned at the
+  // bottom-right of the cursor.
+  kRelativeToCursor,
+};
+
+struct VIEWS_EXPORT TooltipPosition {
+  gfx::Point anchor_point;
+  TooltipPositionBehavior behavior = TooltipPositionBehavior::kRelativeToCursor;
+};
+
 // Tooltip is responsible for showing the tooltip in an appropriate manner.
 // Tooltip is used by TooltipController.
 class VIEWS_EXPORT Tooltip {
@@ -30,9 +44,9 @@
   virtual int GetMaxWidth(const gfx::Point& location) const = 0;
 
   // Updates the text on the tooltip and resizes to fit.
-  virtual void SetText(aura::Window* window,
-                       const std::u16string& tooltip_text,
-                       const gfx::Point& location) = 0;
+  virtual void Update(aura::Window* window,
+                      const std::u16string& tooltip_text,
+                      const TooltipPosition& position) = 0;
 
   // Shows the tooltip at the specified location (in screen coordinates).
   virtual void Show() = 0;
diff --git a/ui/views/corewm/tooltip_aura.cc b/ui/views/corewm/tooltip_aura.cc
index ec220f8..eb9cd6a 100644
--- a/ui/views/corewm/tooltip_aura.cc
+++ b/ui/views/corewm/tooltip_aura.cc
@@ -38,10 +38,6 @@
 // be wrapped.
 constexpr int kTooltipMaxWidthPixels = 800;
 
-// FIXME: get cursor offset from actual cursor size.
-constexpr int kCursorOffsetX = 10;
-constexpr int kCursorOffsetY = 15;
-
 // Paddings
 constexpr int kHorizontalPadding = 8;
 constexpr int kVerticalPaddingTop = 4;
@@ -193,24 +189,40 @@
   widget_->GetTooltipView()->GetAccessibleNodeData(node_data);
 }
 
-gfx::Rect TooltipAura::GetTooltipBounds(const gfx::Point& mouse_pos,
-                                        const gfx::Size& tooltip_size) {
-  gfx::Rect tooltip_rect(mouse_pos, tooltip_size);
-  tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY);
+gfx::Rect TooltipAura::GetTooltipBounds(const gfx::Size& tooltip_size,
+                                        const TooltipPosition& position) {
+  gfx::Rect tooltip_rect(position.anchor_point, tooltip_size);
+  // When the tooltip is showing up as a result of a cursor event, the tooltip
+  // needs to show up at the bottom-right corner of the cursor. When it's not,
+  // it has to be centered with the anchor point with pass it.
+  switch (position.behavior) {
+    case TooltipPositionBehavior::kCentered:
+      tooltip_rect.Offset(-tooltip_size.width() / 2, 0);
+      break;
+    case TooltipPositionBehavior::kRelativeToCursor:
+      tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY);
+      break;
+  }
+
   display::Screen* screen = display::Screen::GetScreen();
-  gfx::Rect display_bounds(screen->GetDisplayNearestPoint(mouse_pos).bounds());
+  gfx::Rect display_bounds(
+      screen->GetDisplayNearestPoint(position.anchor_point).bounds());
 
   // If tooltip is out of bounds on the x axis, we simply shift it
-  // horizontally by the offset.
+  // horizontally by the offset variation.
+  if (tooltip_rect.x() < display_bounds.x()) {
+    int delta = tooltip_rect.x() - display_bounds.x();
+    tooltip_rect.Offset(delta, 0);
+  }
   if (tooltip_rect.right() > display_bounds.right()) {
-    int h_offset = tooltip_rect.right() - display_bounds.right();
-    tooltip_rect.Offset(-h_offset, 0);
+    int delta = tooltip_rect.right() - display_bounds.right();
+    tooltip_rect.Offset(-delta, 0);
   }
 
   // If tooltip is out of bounds on the y axis, we flip it to appear above the
   // mouse cursor instead of below.
   if (tooltip_rect.bottom() > display_bounds.bottom())
-    tooltip_rect.set_y(mouse_pos.y() - tooltip_size.height());
+    tooltip_rect.set_y(position.anchor_point.y() - tooltip_size.height());
 
   tooltip_rect.AdjustToFit(display_bounds);
   return tooltip_rect;
@@ -252,25 +264,25 @@
   return std::min(kTooltipMaxWidthPixels, (display_bounds.width() + 1) / 2);
 }
 
-void TooltipAura::SetText(aura::Window* window,
-                          const std::u16string& tooltip_text,
-                          const gfx::Point& location) {
+void TooltipAura::Update(aura::Window* window,
+                         const std::u16string& tooltip_text,
+                         const TooltipPosition& position) {
   tooltip_window_ = window;
 
   if (!widget_) {
     auto new_tooltip_view = std::make_unique<TooltipView>();
-    new_tooltip_view->SetMaxWidth(GetMaxWidth(location));
+    new_tooltip_view->SetMaxWidth(GetMaxWidth(position.anchor_point));
     new_tooltip_view->SetText(tooltip_text);
     CreateTooltipWidget(
-        GetTooltipBounds(location, new_tooltip_view->GetPreferredSize()));
+        GetTooltipBounds(new_tooltip_view->GetPreferredSize(), position));
     widget_->SetTooltipView(std::move(new_tooltip_view));
     widget_->AddObserver(this);
   } else {
     TooltipView* old_tooltip_view = widget_->GetTooltipView();
-    old_tooltip_view->SetMaxWidth(GetMaxWidth(location));
+    old_tooltip_view->SetMaxWidth(GetMaxWidth(position.anchor_point));
     old_tooltip_view->SetText(tooltip_text);
     widget_->SetBounds(
-        GetTooltipBounds(location, old_tooltip_view->GetPreferredSize()));
+        GetTooltipBounds(old_tooltip_view->GetPreferredSize(), position));
   }
 
   ui::NativeTheme* native_theme = widget_->GetNativeTheme();
diff --git a/ui/views/corewm/tooltip_aura.h b/ui/views/corewm/tooltip_aura.h
index 1b47075..86b2cce6 100644
--- a/ui/views/corewm/tooltip_aura.h
+++ b/ui/views/corewm/tooltip_aura.h
@@ -32,6 +32,10 @@
 // Implementation of Tooltip that shows the tooltip using a Widget and Label.
 class VIEWS_EXPORT TooltipAura : public Tooltip, public WidgetObserver {
  public:
+  // FIXME: get cursor offset from actual cursor size.
+  static constexpr int kCursorOffsetX = 10;
+  static constexpr int kCursorOffsetY = 15;
+
   TooltipAura() = default;
   ~TooltipAura() override;
 
@@ -44,8 +48,8 @@
 
   // Adjusts the bounds given by the arguments to fit inside the desktop
   // and returns the adjusted bounds.
-  gfx::Rect GetTooltipBounds(const gfx::Point& mouse_pos,
-                             const gfx::Size& tooltip_size);
+  gfx::Rect GetTooltipBounds(const gfx::Size& tooltip_size,
+                             const TooltipPosition& position);
 
   // Sets |widget_| to a new instance of TooltipWidget.
   void CreateTooltipWidget(const gfx::Rect& bounds);
@@ -55,9 +59,9 @@
 
   // Tooltip:
   int GetMaxWidth(const gfx::Point& location) const override;
-  void SetText(aura::Window* window,
-               const std::u16string& tooltip_text,
-               const gfx::Point& location) override;
+  void Update(aura::Window* window,
+              const std::u16string& tooltip_text,
+              const TooltipPosition& position) override;
   void Show() override;
   void Hide() override;
   bool IsVisible() override;
diff --git a/ui/views/corewm/tooltip_controller.cc b/ui/views/corewm/tooltip_controller.cc
index 6446a5a..bd8cb72 100644
--- a/ui/views/corewm/tooltip_controller.cc
+++ b/ui/views/corewm/tooltip_controller.cc
@@ -168,6 +168,9 @@
   switch (event->type()) {
     case ui::ET_MOUSE_CAPTURE_CHANGED:
     case ui::ET_MOUSE_EXITED:
+    // TODO(bebeaudr): Keyboard-triggered tooltips that show up right where the
+    // cursor currently is are hidden as soon as they show up because of this
+    // event. Handle this case differently to fix the issue.
     case ui::ET_MOUSE_MOVED:
     case ui::ET_MOUSE_DRAGGED: {
       last_mouse_loc_ = event->location();
diff --git a/ui/views/corewm/tooltip_controller_unittest.cc b/ui/views/corewm/tooltip_controller_unittest.cc
index 5ec56dc..a1d95b7 100644
--- a/ui/views/corewm/tooltip_controller_unittest.cc
+++ b/ui/views/corewm/tooltip_controller_unittest.cc
@@ -19,6 +19,8 @@
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_event_dispatcher.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/font.h"
 #include "ui/gfx/geometry/point.h"
@@ -261,6 +263,119 @@
   EXPECT_EQ(text, base::ASCIIToUTF16(node_data.GetStringAttribute(
                       ax::mojom::StringAttribute::kName)));
 }
+
+TEST_F(TooltipControllerTest, TooltipBounds) {
+  // We don't need a real tootip. Let's just use a custom size and custom point
+  // to test this function.
+  gfx::Size tooltip_size(100, 40);
+  gfx::Rect display_bounds(display::Screen::GetScreen()
+                               ->GetDisplayNearestPoint(gfx::Point(0, 0))
+                               .bounds());
+  gfx::Point anchor_point = display_bounds.CenterPoint();
+
+  // All tests here share the same expected y value.
+  int a_expected_y(anchor_point.y() + TooltipAura::kCursorOffsetY);
+  int b_expected_y(anchor_point.y());
+
+  // 1. The tooltip fits entirely in the window.
+  {
+    // A. When attached to the cursor, the tooltip should be positioned at the
+    // bottom-right corner of the cursor.
+    gfx::Rect bounds =
+        test::TooltipAuraTestApi(tooltip_aura_)
+            .GetTooltipBounds(
+                tooltip_size,
+                {anchor_point, TooltipPositionBehavior::kRelativeToCursor});
+    gfx::Point expected_position(anchor_point.x() + TooltipAura::kCursorOffsetX,
+                                 a_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+
+    // B. When not attached to the cursor, the tooltip should be horizontally
+    // centered with the anchor point.
+    bounds = test::TooltipAuraTestApi(tooltip_aura_)
+                 .GetTooltipBounds(
+                     tooltip_size,
+                     {anchor_point, TooltipPositionBehavior::kCentered});
+    expected_position =
+        gfx::Point(anchor_point.x() - tooltip_size.width() / 2, b_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+  }
+  // 2. The tooltip overflows on the left side of the window.
+  {
+    anchor_point = display_bounds.left_center();
+    anchor_point.Offset(-TooltipAura::kCursorOffsetX - 10, 0);
+
+    // A. When attached to the cursor, the tooltip should be positioned at the
+    // bottom-right corner of the cursor.
+    gfx::Rect bounds =
+        test::TooltipAuraTestApi(tooltip_aura_)
+            .GetTooltipBounds(
+                tooltip_size,
+                {anchor_point, TooltipPositionBehavior::kRelativeToCursor});
+    gfx::Point expected_position(0, a_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+
+    // B. When not attached to the cursor, the tooltip should be horizontally
+    // centered with the anchor point.
+    bounds = test::TooltipAuraTestApi(tooltip_aura_)
+                 .GetTooltipBounds(
+                     tooltip_size,
+                     {anchor_point, TooltipPositionBehavior::kCentered});
+    expected_position = gfx::Point(0, b_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+  }
+  // 3. The tooltip overflows on the right side of the window.
+  {
+    anchor_point = display_bounds.right_center();
+    anchor_point.Offset(10, 0);
+
+    // A. When attached to the cursor, the tooltip should be positioned at the
+    // bottom-right corner of the cursor.
+    gfx::Rect bounds =
+        test::TooltipAuraTestApi(tooltip_aura_)
+            .GetTooltipBounds(
+                tooltip_size,
+                {anchor_point, TooltipPositionBehavior::kRelativeToCursor});
+    gfx::Point expected_position(display_bounds.right() - tooltip_size.width(),
+                                 a_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+
+    // B. When not attached to the cursor, the tooltip should be horizontally
+    // centered with the anchor point.
+    bounds = test::TooltipAuraTestApi(tooltip_aura_)
+                 .GetTooltipBounds(
+                     tooltip_size,
+                     {anchor_point, TooltipPositionBehavior::kCentered});
+    expected_position =
+        gfx::Point(display_bounds.right() - tooltip_size.width(), b_expected_y);
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+  }
+  // 4. The tooltip overflows on the bottom.
+  {
+    anchor_point = display_bounds.bottom_center();
+
+    // A. When attached to the cursor, the tooltip should be positioned at the
+    // bottom-right corner of the cursor.
+    gfx::Rect bounds =
+        test::TooltipAuraTestApi(tooltip_aura_)
+            .GetTooltipBounds(
+                tooltip_size,
+                {anchor_point, TooltipPositionBehavior::kRelativeToCursor});
+    gfx::Point expected_position(anchor_point.x() + TooltipAura::kCursorOffsetX,
+                                 anchor_point.y() - tooltip_size.height());
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+
+    // B. When not attached to the cursor, the tooltip should be horizontally
+    // centered with the anchor point.
+    bounds = test::TooltipAuraTestApi(tooltip_aura_)
+                 .GetTooltipBounds(
+                     tooltip_size,
+                     {anchor_point, TooltipPositionBehavior::kCentered});
+    expected_position = gfx::Point(anchor_point.x() - tooltip_size.width() / 2,
+                                   anchor_point.y() - tooltip_size.height());
+    EXPECT_EQ(bounds, gfx::Rect(expected_position, tooltip_size));
+  }
+}
 #endif
 
 TEST_F(TooltipControllerTest, TooltipsInMultipleViews) {
@@ -587,21 +702,21 @@
 
   // Tooltip:
   int GetMaxWidth(const gfx::Point& location) const override { return 100; }
-  void SetText(aura::Window* window,
-               const std::u16string& tooltip_text,
-               const gfx::Point& location) override {
+  void Update(aura::Window* window,
+              const std::u16string& tooltip_text,
+              const TooltipPosition& position) override {
     tooltip_text_ = tooltip_text;
-    location_ = location;
+    position_ = position;
   }
   void Show() override { is_visible_ = true; }
   void Hide() override { is_visible_ = false; }
   bool IsVisible() override { return is_visible_; }
-  const gfx::Point& location() { return location_; }
+  const TooltipPosition& position() { return position_; }
 
  private:
   bool is_visible_ = false;
   std::u16string tooltip_text_;
-  gfx::Point location_;
+  TooltipPosition position_;
 
   DISALLOW_COPY_AND_ASSIGN(TestTooltip);
 };
@@ -795,14 +910,14 @@
   generator_->MoveMouseRelativeTo(GetWindow(), center);
   EXPECT_TRUE(helper_->IsTooltipVisible());
   EXPECT_EQ(reference_string, helper_->GetTooltipText());
-  gfx::Point tooltip_bounds1 = test_tooltip_->location();
+  gfx::Point tooltip_bounds1 = test_tooltip_->position().anchor_point;
 
   // Test whether the toolbar changes position on mouse over v2
   center = v2->bounds().CenterPoint();
   generator_->MoveMouseRelativeTo(GetWindow(), center);
   EXPECT_TRUE(helper_->IsTooltipVisible());
   EXPECT_EQ(reference_string, helper_->GetTooltipText());
-  gfx::Point tooltip_bounds2 = test_tooltip_->location();
+  gfx::Point tooltip_bounds2 = test_tooltip_->position().anchor_point;
 
   EXPECT_NE(tooltip_bounds1, gfx::Point());
   EXPECT_NE(tooltip_bounds2, gfx::Point());
@@ -813,7 +928,7 @@
   center = v2_1->GetLocalBounds().CenterPoint();
   views::View::ConvertPointToTarget(v2_1, view_, &center);
   generator_->MoveMouseRelativeTo(GetWindow(), center);
-  gfx::Point tooltip_bounds2_1 = test_tooltip_->location();
+  gfx::Point tooltip_bounds2_1 = test_tooltip_->position().anchor_point;
 
   EXPECT_NE(tooltip_bounds2, tooltip_bounds2_1);
   EXPECT_TRUE(helper_->IsTooltipVisible());
@@ -824,7 +939,7 @@
   center = v2_2->GetLocalBounds().CenterPoint();
   views::View::ConvertPointToTarget(v2_2, view_, &center);
   generator_->MoveMouseRelativeTo(GetWindow(), center);
-  gfx::Point tooltip_bounds2_2 = test_tooltip_->location();
+  gfx::Point tooltip_bounds2_2 = test_tooltip_->position().anchor_point;
 
   EXPECT_NE(tooltip_bounds2_1, tooltip_bounds2_2);
   EXPECT_TRUE(helper_->IsTooltipVisible());
@@ -835,14 +950,14 @@
   center = v1_1->GetLocalBounds().CenterPoint();
   views::View::ConvertPointToTarget(v1_1, view_, &center);
   generator_->MoveMouseRelativeTo(GetWindow(), center);
-  gfx::Point tooltip_bounds1_1 = test_tooltip_->location();
+  gfx::Point tooltip_bounds1_1 = test_tooltip_->position().anchor_point;
 
   EXPECT_TRUE(helper_->IsTooltipVisible());
   EXPECT_EQ(reference_string, helper_->GetTooltipText());
 
   center = v1->bounds().CenterPoint();
   generator_->MoveMouseRelativeTo(GetWindow(), center);
-  tooltip_bounds1 = test_tooltip_->location();
+  tooltip_bounds1 = test_tooltip_->position().anchor_point;
 
   EXPECT_NE(tooltip_bounds1_1, tooltip_bounds1);
   EXPECT_EQ(reference_string, helper_->GetTooltipText());
diff --git a/ui/views/corewm/tooltip_state_manager.cc b/ui/views/corewm/tooltip_state_manager.cc
index 869a646..37ff4385 100644
--- a/ui/views/corewm/tooltip_state_manager.cc
+++ b/ui/views/corewm/tooltip_state_manager.cc
@@ -105,10 +105,13 @@
   if (!tooltip_parent_window_)
     return;
 
-  gfx::Point position =
+  gfx::Point anchor_point =
       position_ +
       tooltip_parent_window_->GetBoundsInScreen().OffsetFromOrigin();
-  tooltip_->SetText(tooltip_parent_window_, trimmed_text, position);
+  // TODO(bebeaudr): Don't always pass kRelativeToCursor once we have
+  // keyboard-triggered tooltips.
+  tooltip_->Update(tooltip_parent_window_, trimmed_text,
+                   {anchor_point, TooltipPositionBehavior::kRelativeToCursor});
   tooltip_->Show();
   if (!hide_delay.is_zero()) {
     will_hide_tooltip_timer_.Start(FROM_HERE, hide_delay, this,
diff --git a/ui/views/corewm/tooltip_win.cc b/ui/views/corewm/tooltip_win.cc
index 1a629f6..b0d8cad 100644
--- a/ui/views/corewm/tooltip_win.cc
+++ b/ui/views/corewm/tooltip_win.cc
@@ -77,7 +77,7 @@
 
 void TooltipWin::PositionTooltip() {
   gfx::Point screen_point =
-      display::win::ScreenWin::DIPToScreenPoint(location_);
+      display::win::ScreenWin::DIPToScreenPoint(position_.anchor_point);
   const int cursoroffset = GetCurrentCursorVisibleHeight();
   screen_point.Offset(0, cursoroffset);
 
@@ -86,9 +86,14 @@
   const gfx::Size size(LOWORD(tooltip_size), HIWORD(tooltip_size));
 
   const display::Display display(
-      display::Screen::GetScreen()->GetDisplayNearestPoint(location_));
+      display::Screen::GetScreen()->GetDisplayNearestPoint(
+          position_.anchor_point));
 
   gfx::Rect tooltip_bounds(screen_point, size);
+  // Align the center of the tooltip with the position when the tooltip is not
+  // following the cursor.
+  if (position_.behavior == TooltipPositionBehavior::kCentered)
+    tooltip_bounds.Offset(-size.width() / 2, 0);
   tooltip_bounds.AdjustToFit(display::win::ScreenWin::DIPToScreenRect(
       parent_hwnd_, display.work_area()));
   SetWindowPos(tooltip_hwnd_, nullptr, tooltip_bounds.x(), tooltip_bounds.y(),
@@ -131,14 +136,14 @@
   return (monitor_bounds.width() + 1) / 2;
 }
 
-void TooltipWin::SetText(aura::Window* window,
-                         const std::u16string& tooltip_text,
-                         const gfx::Point& location) {
+void TooltipWin::Update(aura::Window* window,
+                        const std::u16string& tooltip_text,
+                        const TooltipPosition& position) {
   if (!EnsureTooltipWindow())
     return;
 
-  // See comment in header for details on why |location_| is needed.
-  location_ = location;
+  // See comment in header for details on why |position_| is needed.
+  position_ = position;
 
   std::u16string adjusted_text(tooltip_text);
   base::i18n::AdjustStringForLocaleDirection(&adjusted_text);
@@ -146,7 +151,7 @@
   SendMessage(tooltip_hwnd_, TTM_SETTOOLINFO, 0,
               reinterpret_cast<LPARAM>(&toolinfo_));
 
-  int max_width = GetMaxWidth(location_);
+  int max_width = GetMaxWidth(position_.anchor_point);
   SendMessage(tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, max_width);
 }
 
diff --git a/ui/views/corewm/tooltip_win.h b/ui/views/corewm/tooltip_win.h
index 3c736b13..be9088d 100644
--- a/ui/views/corewm/tooltip_win.h
+++ b/ui/views/corewm/tooltip_win.h
@@ -45,9 +45,9 @@
 
   // Tooltip:
   int GetMaxWidth(const gfx::Point& location) const override;
-  void SetText(aura::Window* window,
-               const std::u16string& tooltip_text,
-               const gfx::Point& location) override;
+  void Update(aura::Window* window,
+              const std::u16string& tooltip_text,
+              const TooltipPosition& position) override;
   void Show() override;
   void Hide() override;
   bool IsVisible() override;
@@ -68,10 +68,9 @@
   // Is the tooltip showing?
   bool showing_;
 
-  // Location to show the tooltip at. In order to position the tooltip we need
-  // to know the size. The size is only available from TTN_SHOW, so we have to
-  // cache it.
-  gfx::Point location_;
+  // In order to position the tooltip we need to know the size. The size is only
+  // available from TTN_SHOW, so we have to cache it.
+  TooltipPosition position_;
 
   // What the scale was the last time we overrode the font, to see if we can
   // re-use our previous override.
diff --git a/weblayer/browser/content_view_render_view.cc b/weblayer/browser/content_view_render_view.cc
index 5727b97..d6db49d 100644
--- a/weblayer/browser/content_view_render_view.cc
+++ b/weblayer/browser/content_view_render_view.cc
@@ -190,6 +190,13 @@
                                              jboolean cache_back_buffer) {
   if (cache_back_buffer)
     compositor_->CacheBackBufferForCurrentSurface();
+
+  // When we switch from Chrome to other app we can't detach child surface
+  // controls because it leads to a visible hole: b/157439199. To avoid this we
+  // don't detach surfaces if the surface is going to be destroyed, they will be
+  // detached and freed by OS.
+  compositor_->PreserveChildSurfaceControls();
+
   compositor_->SetSurface(nullptr, false);
 }