diff --git a/BUILD.gn b/BUILD.gn
index 89ec97b..4bec44b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1255,7 +1255,11 @@
                          root_build_dir) + ")" ]
 
     data = [
+      # These tests use //build/android/devil_chromium.py even when !is_android,
+      # so cannot use the helpers in //build/android (they assert(is_android)).
       "//build/android/",
+      "//build/gn_helpers.py",
+      "//build/config/gclient_args.gni",
       "//components/crash/content/tools/generate_breakpad_symbols.py",
       "//third_party/blink/renderer/bindings/scripts/",
       "//third_party/blink/renderer/build/scripts/",
diff --git a/DEPS b/DEPS
index 53dc51a..0d7b5ca 100644
--- a/DEPS
+++ b/DEPS
@@ -204,7 +204,7 @@
   # 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': '94b2c943f7f37e0450e7c5d029fbd22a9d7f6946',
+  'v8_revision': '8a682e08571d84f1d93ef73a07cfac7a089676c9',
   # 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.
@@ -212,15 +212,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '7784fce44d7b02b849b1e72a830296356f2d5791',
+  'angle_revision': 'c4ca12e32e6aef9f5f8eb06c4b361264bec19f8d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'de9e649438ed3cc2ecf5c30ebf08f033d01270e6',
+  'swiftshader_revision': 'aeb3616301c8a2988ede07e4c34e8975da4d5fa0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '876a780b52103aafd97a767a2c0a8e75dbbe2002',
+  'pdfium_revision': 'ab436aebc566b8f86d8cd93037bba2d2c93e0edf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -251,7 +251,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '32b14552d662f28290e2792ce775fcd65397479a',
+  'freetype_revision': '1286f58c299625edd838df2a1deb7cd12f564926',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -267,7 +267,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': '6743d1edbf356c3c9c648353e68fc266c1f18819',
+  'catapult_revision': '11b40137016bc78282f346fe45333676b3ac75fb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # 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 devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'd99bf143c616d7af3274253ba1789c516efc240e',
+  'devtools_frontend_revision': '361d02972a3d37e6e7ebe25e2df51e781da6c0d0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -327,7 +327,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'd2953cff41e76f2a48bb43c0a62b9ce1936496a5',
+  'dawn_revision': '41b3f9c1e4aaaa3d56d205d81e61deaebdd40d75',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -366,7 +366,7 @@
   'ukey2_revision': '0275885d8e6038c39b8a8ca55e75d1d4d1727f47',
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'tint_revision': '627732c40839bb9de6c6cc06f5391cda45107c74',
+  'tint_revision': '4c13659a2a39df7435881b23c00742b557ed6474',
 
   # TODO(crbug.com/941824): The values below need to be kept in sync
   # between //DEPS and //buildtools/DEPS, so if you're updating one,
@@ -1274,7 +1274,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '8dae1eb937cba58791c16a9caaa723676de563a1',
+    Var('chromium_git') + '/openscreen' + '@' + 'a63f1abf172a55dde56701209770e1deb3fc52b1',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '97cfe495bb7a3853266b646d1c79e169387f9c7a',
@@ -1291,7 +1291,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '16973d613fed5c24d63bc4bf6cc9ffe267708ea5',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '8f8e2dc7d50fac8d40137e49c39d1a0c2cc10ed7',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1369,7 +1369,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': '4f-YWuHlGrqS9jy308GUs0eo8DxU3h6PwgpHfNYq290C'
+              'version': 'gt2DKWmtJU6vqOju1UcBB-_Nthud81s3cnZkERzzSEUC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1623,7 +1623,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@196fb8cb97ad1fdae6ea9b66373f3356728c13d7',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a08962107ef6052993af0b1aca418ec6e5bd921a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 836baf0..b43423f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1297,6 +1297,7 @@
     'android_webview/tools/run_cts.pydeps',
     'base/android/jni_generator/jni_generator.pydeps',
     'base/android/jni_generator/jni_registration_generator.pydeps',
+    'build/android/apk_operations.pydeps',
     'build/android/devil_chromium.pydeps',
     'build/android/gyp/aar.pydeps',
     'build/android/gyp/aidl.pydeps',
diff --git a/android_webview/OWNERS b/android_webview/OWNERS
index 7e81d72..7fe521d 100644
--- a/android_webview/OWNERS
+++ b/android_webview/OWNERS
@@ -1,14 +1,12 @@
 boliu@chromium.org
 changwan@chromium.org
 michaelbai@chromium.org
+ntfschr@chromium.org
 tobiasjs@chromium.org
 torne@chromium.org
 
 # Documentation changes:
 per-file *.md=file://android_webview/docs/OWNERS
 
-# Build file refactoring:
-per-file *.gn=ntfschr@chromium.org
-
 # Translation artifacts:
 per-file *.xtb=file://tools/translation/TRANSLATION_OWNERS
diff --git a/android_webview/java/src/org/chromium/android_webview/common/OWNERS b/android_webview/java/src/org/chromium/android_webview/common/OWNERS
deleted file mode 100644
index c2303a9..0000000
--- a/android_webview/java/src/org/chromium/android_webview/common/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file ProductionSupportedFlagList.java=ntfschr@chromium.org
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 6c12dc9..55ca95ff 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -24,7 +24,6 @@
   pydeps_file = "//android_webview/tools/run_cts.pydeps"
   deps = [ "//android_webview:system_webview_apk" ]
   data_deps = [
-    "//build/android:logdog_wrapper_py",
     "//build/android:test_runner_py",
     "//testing/buildbot/filters:webview_cts_tests_filters",
   ]
diff --git a/android_webview/tools/run_cts.pydeps b/android_webview/tools/run_cts.pydeps
index d7fb767..a2d2a28 100644
--- a/android_webview/tools/run_cts.pydeps
+++ b/android_webview/tools/run_cts.pydeps
@@ -13,6 +13,7 @@
 //build/android/pylib/local/emulator/proto/avd_pb2.py
 //build/android/pylib/utils/__init__.py
 //build/android/pylib/utils/test_filter.py
+//build/gn_helpers.py
 //third_party/catapult/common/py_utils/py_utils/__init__.py
 //third_party/catapult/common/py_utils/py_utils/cloud_storage.py
 //third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 47c2ee1..b634ba0 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -325,6 +325,8 @@
     "clipboard/views/clipboard_history_bitmap_item_view.h",
     "clipboard/views/clipboard_history_item_view.cc",
     "clipboard/views/clipboard_history_item_view.h",
+    "clipboard/views/clipboard_history_label.cc",
+    "clipboard/views/clipboard_history_label.h",
     "clipboard/views/clipboard_history_text_item_view.cc",
     "clipboard/views/clipboard_history_text_item_view.h",
     "dbus/ash_dbus_services.cc",
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 99b1449..d0d8fcf 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -1023,6 +1023,14 @@
   select_to_speak_bubble_controller_->Hide();
 }
 
+void AccessibilityControllerImpl::OnSelectToSpeakPanelAction(
+    SelectToSpeakPanelAction action) {
+  if (!features::IsSelectToSpeakNavigationControlEnabled() || !client_) {
+    return;
+  }
+  client_->OnSelectToSpeakPanelAction(action);
+}
+
 bool AccessibilityControllerImpl::IsSwitchAccessRunning() const {
   return switch_access().enabled() || switch_access_disable_dialog_showing_;
 }
@@ -1895,10 +1903,13 @@
       break;
     case FeatureType::kSelectToSpeak:
       select_to_speak_state_ = SelectToSpeakState::kSelectToSpeakStateInactive;
-      if (enabled)
+      if (enabled) {
         MaybeCreateSelectToSpeakEventHandler();
-      else
+      } else {
         select_to_speak_event_handler_.reset();
+        HideSelectToSpeakPanel();
+        select_to_speak_bubble_controller_.reset();
+      }
       break;
     case FeatureType::kStickyKeys:
       Shell::Get()->sticky_keys_controller()->Enable(enabled);
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 44eaad2..4998aee 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -353,6 +353,7 @@
       SelectToSpeakEventHandlerDelegate* delegate) override;
   void ShowSelectToSpeakPanel(const gfx::Rect& anchor, bool is_paused) override;
   void HideSelectToSpeakPanel() override;
+  void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action) override;
   void HideSwitchAccessBackButton() override;
   void HideSwitchAccessMenu() override;
   void ShowSwitchAccessBackButton(const gfx::Rect& anchor) override;
diff --git a/ash/accessibility/test_accessibility_controller_client.cc b/ash/accessibility/test_accessibility_controller_client.cc
index 818edfc..330b461 100644
--- a/ash/accessibility/test_accessibility_controller_client.cc
+++ b/ash/accessibility/test_accessibility_controller_client.cc
@@ -75,6 +75,11 @@
 
 void TestAccessibilityControllerClient::OnSwitchAccessDisabled() {}
 
+void TestAccessibilityControllerClient::OnSelectToSpeakPanelAction(
+    SelectToSpeakPanelAction action) {
+  last_select_to_speak_panel_action_ = action;
+}
+
 int32_t TestAccessibilityControllerClient::GetPlayedEarconAndReset() {
   int32_t tmp = sound_key_;
   sound_key_ = -1;
diff --git a/ash/accessibility/test_accessibility_controller_client.h b/ash/accessibility/test_accessibility_controller_client.h
index 5066659..7ce6c791 100644
--- a/ash/accessibility/test_accessibility_controller_client.h
+++ b/ash/accessibility/test_accessibility_controller_client.h
@@ -42,6 +42,7 @@
       gfx::Point& point_in_screen) override;
   void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen) override;
   void OnSwitchAccessDisabled() override;
+  void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action) override;
 
   int32_t GetPlayedEarconAndReset();
 
@@ -51,12 +52,17 @@
     return select_to_speak_state_change_requests_;
   }
   const std::string& last_alert_message() const { return last_alert_message_; }
+  SelectToSpeakPanelAction last_select_to_speak_panel_action() const {
+    return last_select_to_speak_panel_action_;
+  }
 
  private:
   AccessibilityAlert last_a11y_alert_ = AccessibilityAlert::NONE;
   std::string last_alert_message_;
   int32_t sound_key_ = -1;
   bool is_dictation_active_ = false;
+  SelectToSpeakPanelAction last_select_to_speak_panel_action_ =
+      SelectToSpeakPanelAction::kNone;
 
   ax::mojom::Gesture last_a11y_gesture_ = ax::mojom::Gesture::kNone;
 
diff --git a/ash/app_list/views/page_switcher.cc b/ash/app_list/views/page_switcher.cc
index 7d5bb8b..8f33dd5 100644
--- a/ash/app_list/views/page_switcher.cc
+++ b/ash/app_list/views/page_switcher.cc
@@ -271,7 +271,7 @@
     button->SetSelected(i == model_->selected_page() ? true : false);
   }
   buttons_->SetVisible(model_->total_pages() > 1);
-  Layout();
+  PreferredSizeChanged();
 }
 
 void PageSwitcher::SelectedPageChanged(int old_selected, int new_selected) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index e3cdbf9..60a4ca1 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -3112,6 +3112,9 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_MESSAGE" desc="The message shows in the screenshot or screen recording notification.">
         Show in folder
       </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_LOW_DISK_SPACE_MESSAGE" desc="The message shows in the screen recording notification when recording ends due to low disk space.">
+        Recording ended due to critically low disk space
+      </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_BUTTON_EDIT" desc="The edit button label for the screen capture notification.">
         Edit
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LOW_DISK_SPACE_MESSAGE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LOW_DISK_SPACE_MESSAGE.png.sha1
new file mode 100644
index 0000000..2c167a9
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_LOW_DISK_SPACE_MESSAGE.png.sha1
@@ -0,0 +1 @@
+7a2faef8ba50e589e93a386c6baebe85dcde4e62
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index 87fa10e..56608873 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -23,6 +23,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/status_area_widget.h"
 #include "base/bind.h"
+#include "base/bind_post_task.h"
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/files/file_path.h"
@@ -242,7 +243,7 @@
 CaptureModeController::CaptureModeController(
     std::unique_ptr<CaptureModeDelegate> delegate)
     : delegate_(std::move(delegate)),
-      task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+      blocking_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           // A task priority of BEST_EFFORT is good enough for this runner,
           // since it's used for blocking file IO such as saving the screenshots
           // or the successive webm video chunks received from the recording
@@ -627,7 +628,7 @@
   }
 
   const base::FilePath path = BuildImagePath(timestamp);
-  task_runner_->PostTaskAndReplyWithResult(
+  blocking_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&SaveFile, png_bytes, path),
       base::BindOnce(&CaptureModeController::OnImageFileSaved,
                      weak_ptr_factory_.GetWeakPtr(), png_bytes, path));
@@ -684,6 +685,7 @@
   if (!on_file_saved_callback_.is_null())
     std::move(on_file_saved_callback_).Run(current_video_file_path_);
 
+  low_disk_space_threshold_reached_ = false;
   recording_start_time_ = base::TimeTicks();
   current_video_file_path_.clear();
   video_file_handler_.Reset();
@@ -693,16 +695,20 @@
     const base::FilePath& screen_capture_path,
     const gfx::Image& preview_image,
     const CaptureModeType type) {
+  const bool for_video = type == CaptureModeType::kVideo;
   const base::string16 title = l10n_util::GetStringUTF16(
-      type == CaptureModeType::kImage ? IDS_ASH_SCREEN_CAPTURE_SCREENSHOT_TITLE
-                                      : IDS_ASH_SCREEN_CAPTURE_RECORDING_TITLE);
+      for_video ? IDS_ASH_SCREEN_CAPTURE_RECORDING_TITLE
+                : IDS_ASH_SCREEN_CAPTURE_SCREENSHOT_TITLE);
   const base::string16 message =
-      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_MESSAGE);
+      for_video && low_disk_space_threshold_reached_
+          ? l10n_util::GetStringUTF16(
+                IDS_ASH_SCREEN_CAPTURE_LOW_DISK_SPACE_MESSAGE)
+          : l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_MESSAGE);
 
   message_center::RichNotificationData optional_fields;
   message_center::ButtonInfo edit_button(
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_EDIT));
-  if (type == CaptureModeType::kImage)
+  if (!for_video)
     optional_fields.buttons.push_back(edit_button);
   message_center::ButtonInfo delete_button(
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_DELETE));
@@ -737,7 +743,7 @@
   if (type == CaptureModeType::kVideo) {
     DCHECK_EQ(button_index_value,
               VideoNotificationButtonIndex::BUTTON_DELETE_VIDEO);
-    DeleteFileAsync(task_runner_, screen_capture_path);
+    DeleteFileAsync(blocking_task_runner_, screen_capture_path);
     return;
   }
 
@@ -748,7 +754,7 @@
       delegate_->OpenScreenshotInImageEditor(screen_capture_path);
       break;
     case ScreenshotNotificationButtonIndex::BUTTON_DELETE:
-      DeleteFileAsync(task_runner_, screen_capture_path);
+      DeleteFileAsync(blocking_task_runner_, screen_capture_path);
       break;
     default:
       NOTREACHED();
@@ -813,14 +819,28 @@
   video_recording_watcher_ =
       std::make_unique<VideoRecordingWatcher>(this, capture_params->window);
 
-  // TODO(afakhry): Choose a real buffer capacity when the recording service is
-  // in.
   constexpr size_t kVideoBufferCapacityBytes = 512 * 1024;
+
+  // We use a threshold of 512 MB to end the video recording due to low disk
+  // space, which is the same threshold as that used by the low disk space
+  // notification (See low_disk_notification.cc).
+  constexpr size_t kLowDiskSpaceThresholdInBytes = 512 * 1024 * 1024;
+
+  // The |video_file_handler_| performs all its tasks on the
+  // |blocking_task_runner_|. However, we want the low disk space callback to be
+  // run on the UI thread.
+  base::OnceClosure on_low_disk_space_callback =
+      base::BindPostTask(base::ThreadTaskRunnerHandle::Get(),
+                         base::BindOnce(&CaptureModeController::OnLowDiskSpace,
+                                        weak_ptr_factory_.GetWeakPtr()));
+
   DCHECK(current_video_file_path_.empty());
   recording_start_time_ = base::TimeTicks::Now();
   current_video_file_path_ = BuildVideoPath(base::Time::Now());
   video_file_handler_ = VideoFileHandler::Create(
-      task_runner_, current_video_file_path_, kVideoBufferCapacityBytes);
+      blocking_task_runner_, current_video_file_path_,
+      kVideoBufferCapacityBytes, kLowDiskSpaceThresholdInBytes,
+      std::move(on_low_disk_space_callback));
   video_file_handler_.AsyncCall(&VideoFileHandler::Initialize)
       .Then(on_video_file_status_);
 
@@ -840,4 +860,16 @@
   EndVideoRecording();
 }
 
+void CaptureModeController::OnLowDiskSpace() {
+  DCHECK(base::CurrentUIThread::IsSet());
+
+  low_disk_space_threshold_reached_ = true;
+  // We end the video recording normally (i.e. we don't consider this to be a
+  // failure). The low disk space threashold was chosen to be big enough to
+  // allow the remaining chunks to be saved normally. However,
+  // |low_disk_space_threshold_reached_| will be used to display a different
+  // message in the notification.
+  EndVideoRecording();
+}
+
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h
index 37bc4f3..234d4b1 100644
--- a/ash/capture_mode/capture_mode_controller.h
+++ b/ash/capture_mode/capture_mode_controller.h
@@ -214,13 +214,19 @@
   // allowed to be captured.
   void InterruptVideoRecording();
 
+  // Called back by |video_file_handler_| when it detects a low disk space
+  // condition. In this case we end the video recording to avoid consuming too
+  // much space, and we make sure the video preview notification shows a message
+  // explaining why the recording ended.
+  void OnLowDiskSpace();
+
   std::unique_ptr<CaptureModeDelegate> delegate_;
 
   CaptureModeType type_ = CaptureModeType::kImage;
   CaptureModeSource source_ = CaptureModeSource::kRegion;
 
   // A blocking task runner for file IO operations.
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
 
   mojo::Remote<recording::mojom::RecordingService> recording_service_remote_;
   mojo::Receiver<recording::mojom::RecordingServiceClient>
@@ -236,7 +242,7 @@
   base::FilePath current_video_file_path_;
 
   // Handles the file IO operations of the video file. This enforces doing all
-  // video file related operations on the blocking |task_runner_|.
+  // video file related operations on the |blocking_task_runner_|.
   base::SequenceBound<VideoFileHandler> video_file_handler_;
 
   // We remember the user selected capture region when the source is |kRegion|
@@ -253,6 +259,12 @@
   // will start immediately.
   bool skip_count_down_ui_ = false;
 
+  // True if while writing the video chunks by |video_file_handler_| we detected
+  // a low disk space. This value is used only to determine the message shown to
+  // the user in the video preview notification to explain why the recording was
+  // ended, and is then reset back to false.
+  bool low_disk_space_threshold_reached_ = false;
+
   // Watches events that lead to ending video recording.
   std::unique_ptr<VideoRecordingWatcher> video_recording_watcher_;
 
diff --git a/ash/capture_mode/video_file_handler.cc b/ash/capture_mode/video_file_handler.cc
index 21ed639b..de76e39 100644
--- a/ash/capture_mode/video_file_handler.cc
+++ b/ash/capture_mode/video_file_handler.cc
@@ -6,6 +6,7 @@
 
 #include "base/containers/span.h"
 #include "base/logging.h"
+#include "base/system/sys_info.h"
 #include "base/task/current_thread.h"
 
 namespace ash {
@@ -23,11 +24,14 @@
 base::SequenceBound<VideoFileHandler> VideoFileHandler::Create(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
     const base::FilePath& path,
-    size_t max_buffered_bytes) {
+    size_t max_buffered_bytes,
+    size_t low_disk_space_threshold_bytes,
+    base::OnceClosure on_low_disk_space_callback) {
   DCHECK(base::CurrentUIThread::IsSet());
 
-  return base::SequenceBound<VideoFileHandler>(blocking_task_runner, path,
-                                               max_buffered_bytes);
+  return base::SequenceBound<VideoFileHandler>(
+      blocking_task_runner, path, max_buffered_bytes,
+      low_disk_space_threshold_bytes, std::move(on_low_disk_space_callback));
 }
 
 bool VideoFileHandler::Initialize() {
@@ -94,8 +98,13 @@
 }
 
 VideoFileHandler::VideoFileHandler(const base::FilePath& path,
-                                   size_t max_buffered_bytes)
-    : path_(path), max_buffered_bytes_(max_buffered_bytes) {
+                                   size_t max_buffered_bytes,
+                                   size_t low_disk_space_threshold_bytes,
+                                   base::OnceClosure on_low_disk_space_callback)
+    : path_(path),
+      max_buffered_bytes_(max_buffered_bytes),
+      low_disk_space_threshold_bytes_(low_disk_space_threshold_bytes),
+      on_low_disk_space_callback_(std::move(on_low_disk_space_callback)) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(max_buffered_bytes_);
 
@@ -113,6 +122,21 @@
 
   success_ &=
       file_.WriteAtCurrentPosAndCheck(base::as_bytes(base::make_span(chunk)));
+
+  if (!on_low_disk_space_callback_)
+    return;
+
+  const int64_t remaining_disk_bytes =
+      base::SysInfo::AmountOfFreeDiskSpace(path_);
+  if (remaining_disk_bytes >= 0 &&
+      size_t{remaining_disk_bytes} < low_disk_space_threshold_bytes_) {
+    // Note that a low disk space is not considered an IO failure, and should
+    // not affect |success_|. It simply means that the available disk space is
+    // now below the threshold set by the owner of this object, and should not
+    // affect the actual writing of the chunks until the file system actually
+    // fails to write.
+    std::move(on_low_disk_space_callback_).Run();
+  }
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/video_file_handler.h b/ash/capture_mode/video_file_handler.h
index f7cd1e46..eb87828 100644
--- a/ash/capture_mode/video_file_handler.h
+++ b/ash/capture_mode/video_file_handler.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "base/callback_forward.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/sequence_checker.h"
@@ -58,7 +59,9 @@
   static base::SequenceBound<VideoFileHandler> Create(
       scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
       const base::FilePath& path,
-      size_t max_buffered_bytes);
+      size_t max_buffered_bytes,
+      size_t low_disk_space_threshold_bytes,
+      base::OnceClosure on_low_disk_space_callback);
 
   // Must be done only once before all other operations to open/create the file
   // at |path_| via |file_| in preparation for future writes. The file remains
@@ -85,7 +88,10 @@
  private:
   friend class base::SequenceBound<VideoFileHandler>;
 
-  VideoFileHandler(const base::FilePath& path, size_t max_buffered_bytes);
+  VideoFileHandler(const base::FilePath& path,
+                   size_t max_buffered_bytes,
+                   size_t low_disk_space_threshold_bytes,
+                   base::OnceClosure on_low_disk_space_callback);
   ~VideoFileHandler();
 
   // Appends the given |chunk| to |file_| at its current position, and updates
@@ -101,6 +107,11 @@
   // OOM'in the system.
   const size_t max_buffered_bytes_;
 
+  // The number of bytes if the free disk space goes below which, we will notify
+  // the owner of this object of this condition via the
+  // |on_low_disk_space_callback_|.
+  const size_t low_disk_space_threshold_bytes_;
+
   // The opened file that exists at |path_|. All I/O operations on the file will
   // be done through this object, which must first be initialized by calling
   // |Initialize()|.
@@ -120,6 +131,11 @@
   // repeatedly updated by each write operation.
   bool success_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
 
+  // Called to notify the owner of this object that the low disk space threshold
+  // has been reached.
+  base::OnceClosure on_low_disk_space_callback_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
   // Checker to guarantee certain operations are run on the
   // |blocking_task_runner| given to |Create()|.
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/ash/capture_mode/video_file_handler_unittest.cc b/ash/capture_mode/video_file_handler_unittest.cc
index e2b057c..129d9dc 100644
--- a/ash/capture_mode/video_file_handler_unittest.cc
+++ b/ash/capture_mode/video_file_handler_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/capture_mode/video_file_handler.h"
 
+#include <limits>
 #include <string>
 
 #include "base/bind.h"
@@ -15,6 +16,7 @@
 #include "base/run_loop.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequence_bound.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -45,9 +47,13 @@
   }
 
   // Creates and returns an initialized VideoFileHandler instance.
-  base::SequenceBound<VideoFileHandler> CreateAndInitHandler(size_t capacity) {
-    base::SequenceBound<VideoFileHandler> handler =
-        VideoFileHandler::Create(task_runner(), temp_file(), capacity);
+  base::SequenceBound<VideoFileHandler> CreateAndInitHandler(
+      size_t capacity,
+      size_t low_disk_space_threshold_bytes = 1024,
+      base::OnceClosure on_low_disk_space_callback = base::DoNothing()) {
+    base::SequenceBound<VideoFileHandler> handler = VideoFileHandler::Create(
+        task_runner(), temp_file(), capacity, low_disk_space_threshold_bytes,
+        std::move(on_low_disk_space_callback));
     const bool success =
         RunOnHandlerAndWait(&handler, &VideoFileHandler::Initialize);
     EXPECT_TRUE(success);
@@ -61,12 +67,11 @@
     base::RunLoop run_loop;
     task_runner_->PostTaskAndReplyWithResult(
         FROM_HERE, std::move(task),
-        base::BindOnce(
-            [](base::RunLoop* loop, bool* result, bool success) {
-              *result = success;
-              loop->Quit();
-            },
-            &run_loop, &result));
+        base::OnceCallback<void(bool)>(
+            base::BindLambdaForTesting([&](bool success) {
+              result = success;
+              run_loop.Quit();
+            })));
     run_loop.Run();
     return result;
   }
@@ -86,12 +91,11 @@
                            Method method) const {
     base::RunLoop run_loop;
     bool result = false;
-    handler->AsyncCall(method).Then(base::BindOnce(
-        [](base::RunLoop* loop, bool* result, bool success) {
-          *result = success;
-          loop->Quit();
-        },
-        &run_loop, &result));
+    handler->AsyncCall(method).Then(
+        base::BindLambdaForTesting([&](bool success) {
+          result = success;
+          run_loop.Quit();
+        }));
     run_loop.Run();
     return result;
   }
@@ -218,16 +222,55 @@
   // It's possible to flush the buffer manually.
   base::RunLoop run_loop;
   handler.AsyncCall(&VideoFileHandler::FlushBufferedChunks)
-      .Then(base::BindOnce(
-          [](base::RunLoop* loop, bool success) {
-            EXPECT_TRUE(success);
-            loop->Quit();
-          },
-          &run_loop));
+      .Then(base::BindLambdaForTesting([&](bool success) {
+        EXPECT_TRUE(success);
+        run_loop.Quit();
+      }));
   run_loop.Run();
   file_content = ReadTempFileContent();
   EXPECT_EQ(file_content, chunk_1);
   EXPECT_TRUE(GetSuccessStatusOnUi(&handler));
 }
 
+TEST_F(VideoFileHandlerTest, LowDiskSpace) {
+  constexpr size_t kCapacity = 10;
+  // Simulate 0 disk space remaining.
+  constexpr size_t kLowDiskSpaceThreshold = std::numeric_limits<size_t>::max();
+  bool low_disk_space_threshold_reached = false;
+  base::SequenceBound<VideoFileHandler> handler = CreateAndInitHandler(
+      kCapacity, kLowDiskSpaceThreshold, base::BindLambdaForTesting([&]() {
+        low_disk_space_threshold_reached = true;
+      }));
+  ASSERT_TRUE(handler);
+
+  // Append a chunk smaller than the capacity. Nothing will be written to the
+  // file yet, and the low disk space notification won't be trigged, not until
+  // the buffer is actually written on the next append.
+  std::string chunk_1 = "12345";
+  handler.AsyncCall(&VideoFileHandler::AppendChunk)
+      .WithArgs(chunk_1)
+      .Then(GetIgnoreResultCallback());
+  std::string file_content = ReadTempFileContent();
+  EXPECT_TRUE(file_content.empty());
+  EXPECT_TRUE(GetSuccessStatusOnUi(&handler));
+  EXPECT_FALSE(low_disk_space_threshold_reached);
+
+  std::string chunk_2 = "1234567";
+  handler.AsyncCall(&VideoFileHandler::AppendChunk)
+      .WithArgs(chunk_2)
+      .Then(GetIgnoreResultCallback());
+  file_content = ReadTempFileContent();
+  // Only the buffered chunk will be written, and the low disk space callback
+  // will be triggered.
+  EXPECT_EQ(file_content, chunk_1);
+  EXPECT_TRUE(GetSuccessStatusOnUi(&handler));
+  EXPECT_TRUE(low_disk_space_threshold_reached);
+
+  // Reaching low disk space threshold is not a failure, and it's still possible
+  // to flush the remaining buffered chunks.
+  handler.Reset();
+  file_content = ReadTempFileContent();
+  EXPECT_EQ(file_content, chunk_1 + chunk_2);
+}
+
 }  // namespace ash
diff --git a/ash/clipboard/clipboard_history_util.cc b/ash/clipboard/clipboard_history_util.cc
index 488357d..04cf7e6 100644
--- a/ash/clipboard/clipboard_history_util.cc
+++ b/ash/clipboard/clipboard_history_util.cc
@@ -57,8 +57,11 @@
     case ui::ClipboardInternalFormat::kRtf:
     case ui::ClipboardInternalFormat::kBookmark:
     case ui::ClipboardInternalFormat::kWeb:
-    case ui::ClipboardInternalFormat::kCustom:
       return ClipboardHistoryDisplayFormat::kText;
+    case ui::ClipboardInternalFormat::kCustom:
+      return ContainsFileSystemData(data)
+                 ? ClipboardHistoryDisplayFormat::kFile
+                 : ClipboardHistoryDisplayFormat::kText;
   }
 }
 
diff --git a/ash/clipboard/clipboard_history_util.h b/ash/clipboard/clipboard_history_util.h
index 83f64e7..fdbcb0a 100644
--- a/ash/clipboard/clipboard_history_util.h
+++ b/ash/clipboard/clipboard_history_util.h
@@ -54,7 +54,8 @@
   kText = 0,
   kBitmap = 1,
   kHtml = 2,
-  kMaxValue = 2,
+  kFile = 3,
+  kMaxValue = 3,
 };
 
 // Returns the main format of the specified clipboard `data`.
diff --git a/ash/clipboard/views/clipboard_history_item_view.cc b/ash/clipboard/views/clipboard_history_item_view.cc
index 0053b42..31735ec 100644
--- a/ash/clipboard/views/clipboard_history_item_view.cc
+++ b/ash/clipboard/views/clipboard_history_item_view.cc
@@ -187,6 +187,8 @@
     case ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kHtml:
       return std::make_unique<ClipboardHistoryBitmapItemView>(
           &item, resource_manager, container);
+    case ClipboardHistoryUtil::ClipboardHistoryDisplayFormat::kFile:
+      return std::make_unique<ClipboardHistoryTextItemView>(&item, container);
   }
 }
 
diff --git a/ash/clipboard/views/clipboard_history_item_view.h b/ash/clipboard/views/clipboard_history_item_view.h
index 8c931962c..3d5e075 100644
--- a/ash/clipboard/views/clipboard_history_item_view.h
+++ b/ash/clipboard/views/clipboard_history_item_view.h
@@ -118,7 +118,7 @@
   // it is not allowed to read clipboard data.
   bool IsItemEnabled() const;
 
-  const ClipboardHistoryItem* clipboard_history_item() {
+  const ClipboardHistoryItem* clipboard_history_item() const {
     return clipboard_history_item_;
   }
 
diff --git a/ash/clipboard/views/clipboard_history_label.cc b/ash/clipboard/views/clipboard_history_label.cc
new file mode 100644
index 0000000..ee7f9e5
--- /dev/null
+++ b/ash/clipboard/views/clipboard_history_label.cc
@@ -0,0 +1,48 @@
+// 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 "ash/clipboard/views/clipboard_history_label.h"
+
+#include "ash/style/ash_color_provider.h"
+#include "ash/style/scoped_light_mode_as_default.h"
+
+namespace {
+
+// The preferred height for the label.
+constexpr int kLabelPreferredHeight = 32;
+
+}  // namespace
+
+namespace ash {
+ClipboardHistoryLabel::ClipboardHistoryLabel(const base::string16& text)
+    : views::Label(text) {
+  SetPreferredSize(gfx::Size(INT_MAX, kLabelPreferredHeight));
+  SetFontList(views::style::GetFont(views::style::CONTEXT_TOUCH_MENU,
+                                    views::style::STYLE_PRIMARY));
+  SetMultiLine(false);
+  SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  SetAutoColorReadabilityEnabled(false);
+}
+
+const char* ClipboardHistoryLabel::GetClassName() const {
+  return "ClipboardHistoryLabel";
+}
+
+void ClipboardHistoryLabel::OnThemeChanged() {
+  views::Label::OnThemeChanged();
+
+  // Use the light mode as default because the light mode is the default mode of
+  // the native theme which decides the context menu's background color.
+  // TODO(andrewxu): remove this line after https://crbug.com/1143009 is fixed.
+  ash::ScopedLightModeAsDefault scoped_light_mode_as_default;
+
+  const auto color_type =
+      GetEnabled()
+          ? ash::AshColorProvider::ContentLayerType::kTextColorPrimary
+          : ash::AshColorProvider::ContentLayerType::kTextColorSecondary;
+  SetEnabledColor(
+      ash::AshColorProvider::Get()->GetContentLayerColor(color_type));
+}
+
+}  // namespace ash
diff --git a/ash/clipboard/views/clipboard_history_label.h b/ash/clipboard/views/clipboard_history_label.h
new file mode 100644
index 0000000..ee426bf
--- /dev/null
+++ b/ash/clipboard/views/clipboard_history_label.h
@@ -0,0 +1,27 @@
+// 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 ASH_CLIPBOARD_VIEWS_CLIPBOARD_HISTORY_LABEL_H_
+#define ASH_CLIPBOARD_VIEWS_CLIPBOARD_HISTORY_LABEL_H_
+
+#include "ui/views/controls/label.h"
+
+namespace ash {
+
+// The text label used by the clipboard history menu.
+class ClipboardHistoryLabel : public views::Label {
+ public:
+  explicit ClipboardHistoryLabel(const base::string16& text);
+  ClipboardHistoryLabel(const ClipboardHistoryLabel& rhs) = delete;
+  ClipboardHistoryLabel& operator=(const ClipboardHistoryLabel& rhs) = delete;
+  ~ClipboardHistoryLabel() override = default;
+
+  // views::Label:
+  const char* GetClassName() const override;
+  void OnThemeChanged() override;
+};
+
+}  // namespace ash
+
+#endif  // ASH_CLIPBOARD_VIEWS_CLIPBOARD_HISTORY_LABEL_H_
diff --git a/ash/clipboard/views/clipboard_history_text_item_view.cc b/ash/clipboard/views/clipboard_history_text_item_view.cc
index 70c66ae..7bbdfdd 100644
--- a/ash/clipboard/views/clipboard_history_text_item_view.cc
+++ b/ash/clipboard/views/clipboard_history_text_item_view.cc
@@ -7,20 +7,14 @@
 #include "ash/clipboard/clipboard_history_controller_impl.h"
 #include "ash/clipboard/clipboard_history_resource_manager.h"
 #include "ash/clipboard/clipboard_history_util.h"
+#include "ash/clipboard/views/clipboard_history_label.h"
 #include "ash/shell.h"
-#include "ash/style/ash_color_provider.h"
-#include "ash/style/scoped_light_mode_as_default.h"
 #include "base/metrics/histogram_macros.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/menu/menu_config.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/view_class_properties.h"
 
 namespace {
 
-// The preferred height for the label.
-constexpr int kLabelPreferredHeight = 32;
-
 // The margins of the delete button.
 constexpr gfx::Insets kDeleteButtonMargins =
     gfx::Insets(/*top=*/0, /*left=*/8, /*bottom=*/0, /*right=*/4);
@@ -34,8 +28,20 @@
 class ClipboardHistoryTextItemView::TextContentsView
     : public ClipboardHistoryTextItemView::ContentsView {
  public:
-  explicit TextContentsView(ClipboardHistoryItemView* container)
-      : ContentsView(container) {}
+  explicit TextContentsView(ClipboardHistoryTextItemView* container)
+      : ContentsView(container) {
+    auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
+        views::BoxLayout::Orientation::kHorizontal));
+    layout->set_cross_axis_alignment(
+        views::BoxLayout::CrossAxisAlignment::kCenter);
+
+    auto* label =
+        AddChildView(std::make_unique<ClipboardHistoryLabel>(container->text_));
+    layout->SetFlexForView(label, /*flex_weight=*/1);
+    label->SetEnabled(container->IsItemEnabled());
+
+    InstallDeleteButton();
+  }
   TextContentsView(const TextContentsView& rhs) = delete;
   TextContentsView& operator=(const TextContentsView& rhs) = delete;
   ~TextContentsView() override = default;
@@ -70,25 +76,7 @@
 
 std::unique_ptr<ClipboardHistoryTextItemView::ContentsView>
 ClipboardHistoryTextItemView::CreateContentsView() {
-  auto contents_view = std::make_unique<TextContentsView>(this);
-  auto* layout =
-      contents_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kHorizontal));
-  layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kCenter);
-
-  label_ = contents_view->AddChildView(std::make_unique<views::Label>(text_));
-  label_->SetPreferredSize(gfx::Size(INT_MAX, kLabelPreferredHeight));
-  label_->SetFontList(views::style::GetFont(views::style::CONTEXT_TOUCH_MENU,
-                                            views::style::STYLE_PRIMARY));
-  label_->SetMultiLine(false);
-  label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  label_->SetAutoColorReadabilityEnabled(/*enabled=*/false);
-  layout->SetFlexForView(label_, /*flex_weight=*/1);
-
-  contents_view->InstallDeleteButton();
-
-  return contents_view;
+  return std::make_unique<TextContentsView>(this);
 }
 
 base::string16 ClipboardHistoryTextItemView::GetAccessibleName() const {
@@ -99,23 +87,4 @@
   return "ClipboardHistoryTextItemView";
 }
 
-void ClipboardHistoryTextItemView::OnThemeChanged() {
-  // Use the light mode as default because the light mode is the default mode of
-  // the native theme which decides the context menu's background color.
-  // TODO(andrewxu): remove this line after https://crbug.com/1143009 is fixed.
-  ScopedLightModeAsDefault scoped_light_mode_as_default;
-
-  ClipboardHistoryItemView::OnThemeChanged();
-
-  // Calculate the text color.
-  const auto color_type =
-      IsItemEnabled() ? AshColorProvider::ContentLayerType::kTextColorPrimary
-                      : AshColorProvider::ContentLayerType::kTextColorSecondary;
-  const SkColor text_color =
-      AshColorProvider::Get()->GetContentLayerColor(color_type);
-
-  label_->SetEnabledColor(text_color);
-  label_->SchedulePaint();
-}
-
 }  // namespace ash
diff --git a/ash/clipboard/views/clipboard_history_text_item_view.h b/ash/clipboard/views/clipboard_history_text_item_view.h
index eae0a5c..fd660c0 100644
--- a/ash/clipboard/views/clipboard_history_text_item_view.h
+++ b/ash/clipboard/views/clipboard_history_text_item_view.h
@@ -8,7 +8,6 @@
 #include "ash/clipboard/views/clipboard_history_item_view.h"
 
 namespace views {
-class Label;
 class MenuItemView;
 }  // namespace views
 
@@ -33,12 +32,9 @@
   std::unique_ptr<ContentsView> CreateContentsView() override;
   base::string16 GetAccessibleName() const override;
   const char* GetClassName() const override;
-  void OnThemeChanged() override;
 
   // Text to show.
   const base::string16 text_;
-
-  views::Label* label_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/accessibility_controller.h b/ash/public/cpp/accessibility_controller.h
index edaf4cd..2a1a987 100644
--- a/ash/public/cpp/accessibility_controller.h
+++ b/ash/public/cpp/accessibility_controller.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "ash/public/cpp/accelerators.h"
+#include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/public/cpp/ash_public_export.h"
 #include "base/macros.h"
 #include "base/strings/string16.h"
@@ -73,6 +74,9 @@
   // Hides the Select-to-Speak panel.
   virtual void HideSelectToSpeakPanel() = 0;
 
+  // Dispatches event to notify Select-to-speak that a panel action occurred.
+  virtual void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action) = 0;
+
   // Hides the Switch Access back button.
   virtual void HideSwitchAccessBackButton() = 0;
 
diff --git a/ash/public/cpp/accessibility_controller_client.h b/ash/public/cpp/accessibility_controller_client.h
index 18a1711..15bf025 100644
--- a/ash/public/cpp/accessibility_controller_client.h
+++ b/ash/public/cpp/accessibility_controller_client.h
@@ -23,6 +23,7 @@
 namespace ash {
 
 enum class AccessibilityAlert;
+enum class SelectToSpeakPanelAction;
 
 // Interface for Ash to request accessibility service from its client, Chrome.
 class ASH_PUBLIC_EXPORT AccessibilityControllerClient {
@@ -92,6 +93,10 @@
   // and before the disable dialog is accepted, so that users can use Switch
   // Access to cancel or accept the dialog.
   virtual void OnSwitchAccessDisabled() = 0;
+
+  // Called when an action occurs (such as button click) on the Select-to-speak
+  // floating control panel.
+  virtual void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action) = 0;
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/accessibility_controller_enums.h b/ash/public/cpp/accessibility_controller_enums.h
index e8f58ef..ba1b888 100644
--- a/ash/public/cpp/accessibility_controller_enums.h
+++ b/ash/public/cpp/accessibility_controller_enums.h
@@ -83,6 +83,25 @@
   kSelectToSpeakStateSpeaking,
 };
 
+enum class SelectToSpeakPanelAction {
+  // No action.
+  kNone,
+  // Navigate to previous paragraph/block.
+  kPreviousParagraph,
+  // Navigate to previous sentence.
+  kPreviousSentence,
+  // Pause text-to-speech.
+  kPause,
+  // Resumes text-to-speech.
+  kResume,
+  // Navigate to next sentence.
+  kNextSentence,
+  // Navigate to next paragraph/block.
+  kNextParagraph,
+  // Exit Select-to-speak.
+  kExit,
+};
+
 enum class SwitchAccessCommand {
   // Do not perform a command.
   kNone,
diff --git a/ash/shelf/shelf_app_button.cc b/ash/shelf/shelf_app_button.cc
index 22e43bc..95a06fe 100644
--- a/ash/shelf/shelf_app_button.cc
+++ b/ash/shelf/shelf_app_button.cc
@@ -182,7 +182,7 @@
     canvas->SaveLayerAlpha(SK_AlphaOPAQUE);
 
     DCHECK_EQ(width(), height());
-    DCHECK_EQ(kNotificationIndicatorRadiusDip, width() / 2);
+    float radius = width() / 2.0f;
     const float dsf = canvas->UndoDeviceScaleFactor();
     const int kStrokeWidthPx = 1;
     gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
@@ -192,15 +192,12 @@
     cc::PaintFlags flags;
     flags.setColor(indicator_color_);
     flags.setAntiAlias(true);
-    canvas->DrawCircle(
-        center, dsf * kNotificationIndicatorRadiusDip - kStrokeWidthPx, flags);
+    canvas->DrawCircle(center, dsf * radius - kStrokeWidthPx, flags);
 
     // Stroke the border.
     flags.setColor(SkColorSetA(SK_ColorBLACK, 0x4D));
     flags.setStyle(cc::PaintFlags::kStroke_Style);
-    canvas->DrawCircle(
-        center, dsf * kNotificationIndicatorRadiusDip - kStrokeWidthPx / 2.0f,
-        flags);
+    canvas->DrawCircle(center, dsf * radius - kStrokeWidthPx / 2.0f, flags);
   }
 
   void SetColor(SkColor new_color) {
@@ -733,6 +730,16 @@
   return gfx::ToRoundedRect(icon_view_bounds);
 }
 
+gfx::Rect ShelfAppButton::GetNotificationIndicatorBounds(float icon_scale) {
+  gfx::Rect scaled_icon_view_bounds = GetIconViewBounds(icon_scale);
+  float diameter =
+      std::floor(kNotificationIndicatorRadiusDip * icon_scale * 2.0f);
+  return gfx::Rect(scaled_icon_view_bounds.right() - diameter -
+                       kNotificationIndicatorPadding,
+                   scaled_icon_view_bounds.y() + kNotificationIndicatorPadding,
+                   diameter, diameter);
+}
+
 void ShelfAppButton::Layout() {
   Shelf* shelf = shelf_view_->shelf();
   gfx::Rect icon_view_bounds = GetIconViewBounds(icon_scale_);
@@ -742,17 +749,13 @@
 
   icon_view_->SetBoundsRect(icon_view_bounds);
 
-  // The indicators should be aligned with the icon, not the icon + shadow.
-  gfx::Point indicator_midpoint = icon_view_bounds.CenterPoint();
   if (is_notification_indicator_enabled_) {
-    notification_indicator_->SetBoundsRect(gfx::Rect(
-        icon_view_bounds.right() - 2 * kNotificationIndicatorRadiusDip -
-            kNotificationIndicatorPadding,
-        icon_view_bounds.y() + kNotificationIndicatorPadding,
-        kNotificationIndicatorRadiusDip * 2,
-        kNotificationIndicatorRadiusDip * 2));
+    notification_indicator_->SetBoundsRect(
+        GetNotificationIndicatorBounds(icon_scale_));
   }
 
+  // The indicators should be aligned with the icon, not the icon + shadow.
+  gfx::Point indicator_midpoint = icon_view_bounds.CenterPoint();
   switch (shelf->alignment()) {
     case ShelfAlignment::kBottom:
     case ShelfAlignment::kBottomLocked:
@@ -944,12 +947,31 @@
     settings.AddObserver(this);
     icon_view_->layer()->SetTransform(GetScaleTransform(kAppIconScale));
   }
+
+  // Animate the notification indicator alongside the |icon_view_|.
+  if (notification_indicator_) {
+    gfx::RectF pre_scale(GetNotificationIndicatorBounds(1.0));
+    gfx::RectF post_scale(GetNotificationIndicatorBounds(kAppIconScale));
+    gfx::Transform scale_transform =
+        gfx::TransformBetweenRects(post_scale, pre_scale);
+
+    if (scale_up)
+      notification_indicator_->layer()->SetTransform(scale_transform);
+    ui::ScopedLayerAnimationSettings notification_settings(
+        notification_indicator_->layer()->GetAnimator());
+    notification_settings.SetTransitionDuration(
+        base::TimeDelta::FromMilliseconds(kDragDropAppIconScaleTransitionMs));
+    notification_indicator_->layer()->SetTransform(scale_up ? gfx::Transform()
+                                                            : scale_transform);
+  }
 }
 
 void ShelfAppButton::OnImplicitAnimationsCompleted() {
   icon_scale_ = 1.0f;
   SetImage(icon_image_);
   icon_view_->layer()->SetTransform(gfx::Transform());
+  if (notification_indicator_)
+    notification_indicator_->layer()->SetTransform(gfx::Transform());
 }
 
 void ShelfAppButton::SetInkDropAnimationStarted(bool started) {
diff --git a/ash/shelf/shelf_app_button.h b/ash/shelf/shelf_app_button.h
index 246d794..e44051e 100644
--- a/ash/shelf/shelf_app_button.h
+++ b/ash/shelf/shelf_app_button.h
@@ -162,6 +162,9 @@
   // Calculates the icon bounds for an icon scaled by |icon_scale|.
   gfx::Rect GetIconViewBounds(float icon_scale);
 
+  // Calculates the notification indicator bounds when scaled by |scale|.
+  gfx::Rect GetNotificationIndicatorBounds(float scale);
+
   // Calculates the transform between the icon scaled by |icon_scale| and the
   // normal size icon.
   gfx::Transform GetScaleTransform(float icon_scale);
diff --git a/ash/system/accessibility/select_to_speak_menu_bubble_controller_unittest.cc b/ash/system/accessibility/select_to_speak_menu_bubble_controller_unittest.cc
index dcffc08..7bd5532 100644
--- a/ash/system/accessibility/select_to_speak_menu_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/select_to_speak_menu_bubble_controller_unittest.cc
@@ -5,6 +5,8 @@
 #include "ash/system/accessibility/select_to_speak_menu_bubble_controller.h"
 
 #include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/accessibility/test_accessibility_controller_client.h"
+#include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/accessibility/floating_menu_button.h"
@@ -17,6 +19,14 @@
 #include "ui/display/screen.h"
 
 namespace ash {
+namespace {
+
+ui::GestureEvent CreateTapEvent(int x = 0, int y = 0) {
+  return ui::GestureEvent(x, y, 0, base::TimeTicks(),
+                          ui::GestureEventDetails(ui::ET_GESTURE_TAP));
+}
+
+}  // namespace
 
 class SelectToSpeakMenuBubbleControllerTest : public AshTestBase {
  public:
@@ -37,6 +47,8 @@
         true);
   }
 
+  void TearDown() override { AshTestBase::TearDown(); }
+
   AccessibilityControllerImpl* GetAccessibilitController() {
     return Shell::Get()->accessibility_controller();
   }
@@ -62,13 +74,17 @@
         menu_view->GetViewByID(static_cast<int>(view_id)));
   }
 
+  void ShowSelectToSpeakPanel(bool is_paused) {
+    gfx::Rect anchor_rect(10, 10, 0, 0);
+    GetAccessibilitController()->ShowSelectToSpeakPanel(anchor_rect, is_paused);
+  }
+
+ protected:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(SelectToSpeakMenuBubbleControllerTest, ShowSelectToSpeakPanel_paused) {
-  gfx::Rect anchor_rect(10, 10, 0, 0);
-  GetAccessibilitController()->ShowSelectToSpeakPanel(anchor_rect,
-                                                      /* isPaused= */ true);
+  ShowSelectToSpeakPanel(/* is_paused= */ true);
   EXPECT_TRUE(GetMenuView());
 
   FloatingMenuButton* pause_button =
@@ -80,9 +96,7 @@
 
 TEST_F(SelectToSpeakMenuBubbleControllerTest,
        ShowSelectToSpeakPanel_notPaused) {
-  gfx::Rect anchor_rect(10, 10, 0, 0);
-  GetAccessibilitController()->ShowSelectToSpeakPanel(anchor_rect,
-                                                      /* isPaused= */ false);
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
   EXPECT_TRUE(GetMenuView());
 
   FloatingMenuButton* pause_button =
@@ -93,12 +107,94 @@
 }
 
 TEST_F(SelectToSpeakMenuBubbleControllerTest, HideSelectToSpeakPanel) {
-  gfx::Rect anchor_rect(10, 10, 0, 0);
-  GetAccessibilitController()->ShowSelectToSpeakPanel(anchor_rect,
-                                                      /* isPaused= */ false);
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
   GetAccessibilitController()->HideSelectToSpeakPanel();
   EXPECT_TRUE(GetMenuView());
   EXPECT_FALSE(GetBubbleWidget()->IsVisible());
 }
 
+TEST_F(SelectToSpeakMenuBubbleControllerTest, PauseButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kPause);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kPause);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, ResumeButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ true);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kPause);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kResume);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, PrevParagraphButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kPrevParagraph);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kPreviousParagraph);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, PrevSentenceButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kPrevSentence);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kPreviousSentence);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, NextParagraphButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kNextParagraph);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kNextParagraph);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, NextSentenceButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kNextSentence);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kNextSentence);
+}
+
+TEST_F(SelectToSpeakMenuBubbleControllerTest, StopButtonPressed) {
+  TestAccessibilityControllerClient client;
+  ShowSelectToSpeakPanel(/* is_paused= */ false);
+  FloatingMenuButton* button =
+      GetMenuButton(SelectToSpeakMenuView::ButtonId::kStop);
+  ui::GestureEvent event = CreateTapEvent();
+  button->OnGestureEvent(&event);
+
+  EXPECT_EQ(client.last_select_to_speak_panel_action(),
+            SelectToSpeakPanelAction::kExit);
+}
+
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/system/accessibility/select_to_speak_menu_view.cc b/ash/system/accessibility/select_to_speak_menu_view.cc
index ed2ac09..318aa62 100644
--- a/ash/system/accessibility/select_to_speak_menu_view.cc
+++ b/ash/system/accessibility/select_to_speak_menu_view.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/accessibility/select_to_speak_menu_view.h"
 
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -30,6 +32,31 @@
 constexpr int kStopButtonPadding = 14;
 constexpr int kSeparatorHeight = 16;
 
+SelectToSpeakPanelAction PanelActionForButtonID(int button_id, bool is_paused) {
+  auto button_enum = static_cast<SelectToSpeakMenuView::ButtonId>(button_id);
+  switch (button_enum) {
+    case SelectToSpeakMenuView::ButtonId::kPrevParagraph:
+      return SelectToSpeakPanelAction::kPreviousParagraph;
+    case SelectToSpeakMenuView::ButtonId::kPrevSentence:
+      return SelectToSpeakPanelAction::kPreviousSentence;
+    case SelectToSpeakMenuView::ButtonId::kPause:
+      // Pause button toggles pause/resume state.
+      if (is_paused)
+        return SelectToSpeakPanelAction::kResume;
+      else
+        return SelectToSpeakPanelAction::kPause;
+    case SelectToSpeakMenuView::ButtonId::kNextParagraph:
+      return SelectToSpeakPanelAction::kNextParagraph;
+    case SelectToSpeakMenuView::ButtonId::kNextSentence:
+      return SelectToSpeakPanelAction::kNextSentence;
+    case SelectToSpeakMenuView::ButtonId::kStop:
+      return SelectToSpeakPanelAction::kExit;
+  }
+
+  NOTREACHED();
+  return SelectToSpeakPanelAction::kNone;
+}
+
 }  // namespace
 
 SelectToSpeakMenuView::SelectToSpeakMenuView() {
@@ -127,10 +154,13 @@
   pause_button_->SetTooltipText(
       l10n_util::GetStringUTF16(is_paused ? IDS_ASH_SELECT_TO_SPEAK_RESUME
                                           : IDS_ASH_SELECT_TO_SPEAK_PAUSE));
+  is_paused_ = is_paused;
 }
 
 void SelectToSpeakMenuView::OnButtonPressed(views::Button* sender) {
-  // TODO(crbug.com/1143814): Handle button clicks.
+  SelectToSpeakPanelAction action =
+      PanelActionForButtonID(sender->GetID(), is_paused_);
+  Shell::Get()->accessibility_controller()->OnSelectToSpeakPanelAction(action);
 }
 
 BEGIN_METADATA(SelectToSpeakMenuView, views::BoxLayoutView)
diff --git a/ash/system/accessibility/select_to_speak_menu_view.h b/ash/system/accessibility/select_to_speak_menu_view.h
index bbc27bf..354dd91 100644
--- a/ash/system/accessibility/select_to_speak_menu_view.h
+++ b/ash/system/accessibility/select_to_speak_menu_view.h
@@ -47,6 +47,7 @@
   FloatingMenuButton* next_sentence_button_ = nullptr;
   FloatingMenuButton* next_paragraph_button_ = nullptr;
   FloatingMenuButton* stop_button_ = nullptr;
+  bool is_paused_ = false;
 };
 
 BEGIN_VIEW_BUILDER(/* no export */, SelectToSpeakMenuView, views::BoxLayoutView)
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index bb5ef18..2d04c66 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -19,6 +19,7 @@
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_util.h"
@@ -62,9 +63,6 @@
 constexpr int kNoItemsIndicatorHorizontalPaddingDp = 16;
 constexpr int kNoItemsIndicatorRoundingDp = 16;
 constexpr int kNoItemsIndicatorVerticalPaddingDp = 8;
-constexpr SkColor kNoItemsIndicatorBackgroundColor =
-    SkColorSetA(SK_ColorBLACK, 204);
-constexpr SkColor kNoItemsIndicatorTextColor = SK_ColorWHITE;
 
 // Values for scrolling the grid by using the keyboard.
 // TODO(sammiequon): See if we can use the same values used for web scrolling.
@@ -1217,9 +1215,11 @@
     params.name = "OverviewNoWindowsLabel";
     params.horizontal_padding = kNoItemsIndicatorHorizontalPaddingDp;
     params.vertical_padding = kNoItemsIndicatorVerticalPaddingDp;
-    params.background_color = kNoItemsIndicatorBackgroundColor;
-    params.foreground_color = kNoItemsIndicatorTextColor;
-    params.rounding_dp = kNoItemsIndicatorRoundingDp;
+    auto* color_provider = AshColorProvider::Get();
+    params.background_color = color_provider->GetBaseLayerColor(
+        AshColorProvider::BaseLayerType::kTransparent80);
+    params.foreground_color = color_provider->GetContentLayerColor(
+        AshColorProvider::ContentLayerType::kTextColorPrimary);
     params.preferred_height = kNoItemsIndicatorHeightDp;
     params.message_id = IDS_ASH_OVERVIEW_NO_RECENT_ITEMS;
     params.parent = Shell::GetPrimaryRootWindow()->GetChildById(
@@ -1227,6 +1227,10 @@
     params.hide_in_mini_view = true;
     no_windows_widget_ = std::make_unique<RoundedLabelWidget>();
     no_windows_widget_->Init(std::move(params));
+    no_windows_widget_->GetLayer()->SetRoundedCornerRadius(
+        gfx::RoundedCornersF(kNoItemsIndicatorRoundingDp));
+    no_windows_widget_->GetLayer()->SetBackgroundBlur(
+        static_cast<float>(AshColorProvider::LayerBlurSigma::kBlurDefault));
 
     aura::Window* widget_window = no_windows_widget_->GetNativeWindow();
     widget_window->parent()->StackChildAtBottom(widget_window);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 2ec746a4..777221a 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1678,6 +1678,7 @@
     sources += [
       "files/file_path_watcher_stub.cc",
       "process/process_metrics_nacl.cc",
+      "process/process_stubs.cc",
       "sync_socket_nacl.cc",
       "threading/platform_thread_linux.cc",
     ]
@@ -2026,6 +2027,7 @@
       "message_loop/message_pump_mac.mm",
       "power_monitor/power_monitor_device_source_ios.mm",
       "process/memory_stubs.cc",
+      "process/process_stubs.cc",
       "strings/sys_string_conversions_mac.mm",
       "synchronization/waitable_event_mac.cc",
       "system/sys_info_ios.mm",
diff --git a/base/allocator/partition_allocator/page_allocator_internals_posix.h b/base/allocator/partition_allocator/page_allocator_internals_posix.h
index 756990a..f05497e0 100644
--- a/base/allocator/partition_allocator/page_allocator_internals_posix.h
+++ b/base/allocator/partition_allocator/page_allocator_internals_posix.h
@@ -216,29 +216,12 @@
     void* address,
     size_t length,
     PageAccessibilityConfiguration accessibility) {
-  if (!HANDLE_EINTR(mprotect(address, length, GetAccessFlags(accessibility))))
-    return;
-
-  // mprotect() failed, let's get more data on errno in crash reports. Values
-  // taken from man mprotect(2) on Linux:
-  switch (errno) {
-    case EACCES:
-      PCHECK(false);
-      break;
-    case EINVAL:
-      PCHECK(false);
-      break;
-    case ENOMEM:
-      PCHECK(false);
-      break;
-    default:
-      PCHECK(false);
-      break;
-  }
+  PA_PCHECK(0 == HANDLE_EINTR(
+                     mprotect(address, length, GetAccessFlags(accessibility))));
 }
 
 void FreePagesInternal(void* address, size_t length) {
-  PCHECK(!munmap(address, length));
+  PA_PCHECK(0 == munmap(address, length));
 }
 
 void* TrimMappingInternal(void* base,
@@ -294,7 +277,7 @@
     // MADV_FREE_REUSABLE sometimes fails, so fall back to MADV_DONTNEED.
     ret = madvise(address, length, MADV_DONTNEED);
   }
-  PCHECK(0 == ret);
+  PA_PCHECK(ret == 0);
 #else
   // We have experimented with other flags, but with suboptimal results.
   //
@@ -302,7 +285,7 @@
   // performance benefits unclear.
   //
   // Therefore, we just do the simple thing: MADV_DONTNEED.
-  PCHECK(!madvise(address, length, MADV_DONTNEED));
+  PA_PCHECK(0 == madvise(address, length, MADV_DONTNEED));
 #endif
 }
 
diff --git a/base/allocator/partition_allocator/partition_alloc_check.h b/base/allocator/partition_allocator/partition_alloc_check.h
index 404695c..8b57f22 100644
--- a/base/allocator/partition_allocator/partition_alloc_check.h
+++ b/base/allocator/partition_allocator/partition_alloc_check.h
@@ -8,10 +8,12 @@
 #include "base/allocator/buildflags.h"
 #include "base/allocator/partition_allocator/page_allocator_constants.h"
 #include "base/check.h"
+#include "base/debug/alias.h"
+#include "base/immediate_crash.h"
 
 // When PartitionAlloc is used as the default allocator, we cannot use the
 // regular (D)CHECK() macros, as they allocate internally. When an assertion is
-// triggered, they format strings, leading to reentrency in the code, which none
+// triggered, they format strings, leading to reentrancy in the code, which none
 // of PartitionAlloc is designed to support (and especially not for error
 // paths).
 //
@@ -29,9 +31,17 @@
 #define PA_DCHECK(condition) EAT_CHECK_STREAM_PARAMS(!(condition))
 #endif  // DCHECK_IS_ON()
 
+#define PA_PCHECK(condition)    \
+  if (!(condition)) {           \
+    int error = errno;          \
+    base::debug::Alias(&error); \
+    IMMEDIATE_CRASH();          \
+  }
+
 #else
 #define PA_CHECK(condition) CHECK(condition)
 #define PA_DCHECK(condition) DCHECK(condition)
+#define PA_PCHECK(condition) PCHECK(condition)
 #endif  // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 
 #if defined(PAGE_ALLOCATOR_CONSTANTS_ARE_CONSTEXPR)
diff --git a/base/process/process.h b/base/process/process.h
index 239993dc..73e31ab 100644
--- a/base/process/process.h
+++ b/base/process/process.h
@@ -90,6 +90,11 @@
   // Returns a second object that represents this process.
   Process Duplicate() const;
 
+  // Relinquishes ownership of the handle and sets this to kNullProcessHandle.
+  // The result may be a pseudo-handle, depending on the OS and value stored in
+  // this.
+  ProcessHandle Release() WARN_UNUSED_RESULT;
+
   // Get the PID for this process.
   ProcessId Pid() const;
 
@@ -160,7 +165,7 @@
   // process though that should be avoided.
   void Exited(int exit_code) const;
 
-#if defined(OS_APPLE)
+#if defined(OS_MAC)
   // The Mac needs a Mach port in order to manipulate a process's priority,
   // and there's no good way to get that from base given the pid. These Mac
   // variants of the IsProcessBackgrounded and SetProcessBackgrounded API take
diff --git a/base/process/process_fuchsia.cc b/base/process/process_fuchsia.cc
index c04e29d..3f71ea8 100644
--- a/base/process/process_fuchsia.cc
+++ b/base/process/process_fuchsia.cc
@@ -154,6 +154,21 @@
   return Process(out.release());
 }
 
+ProcessHandle Process::Release() {
+  if (is_current()) {
+    // Caller expects to own the reference, so duplicate the self handle.
+    zx::process handle;
+    zx_status_t result =
+        zx::process::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &handle);
+    if (result != ZX_OK) {
+      return kNullProcessHandle;
+    }
+    is_current_process_ = false;
+    return handle.release();
+  }
+  return process_.release();
+}
+
 ProcessId Process::Pid() const {
   DCHECK(IsValid());
   return GetProcId(Handle());
diff --git a/base/process/process_posix.cc b/base/process/process_posix.cc
index 3f73b82f..79235e0 100644
--- a/base/process/process_posix.cc
+++ b/base/process/process_posix.cc
@@ -10,6 +10,8 @@
 #include <sys/resource.h>
 #include <sys/wait.h>
 
+#include <utility>
+
 #include "base/clang_profiling_buildflags.h"
 #include "base/debug/activity_tracker.h"
 #include "base/files/scoped_file.h"
@@ -293,6 +295,10 @@
   return Process(process_);
 }
 
+ProcessHandle Process::Release() {
+  return std::exchange(process_, kNullProcessHandle);
+}
+
 ProcessId Process::Pid() const {
   DCHECK(IsValid());
   return GetProcId(process_);
diff --git a/base/process/process_stubs.cc b/base/process/process_stubs.cc
new file mode 100644
index 0000000..24d4df1
--- /dev/null
+++ b/base/process/process_stubs.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 "base/process/process.h"
+
+#include <limits>
+
+namespace base {
+
+static constexpr ProcessHandle kCurrentProcessHandle =
+    std::numeric_limits<ProcessHandle>::max();
+
+Process::Process(ProcessHandle handle) : process_(handle) {
+  DCHECK(handle == kNullProcessHandle || handle == kCurrentProcessHandle);
+}
+
+Process::Process(Process&& other) : process_(other.process_) {
+  other.Close();
+}
+
+Process::~Process() = default;
+
+Process& Process::operator=(Process&& other) {
+  process_ = other.process_;
+  other.Close();
+  return *this;
+}
+
+// static
+Process Process::Current() {
+  return Process(kCurrentProcessHandle);
+}
+
+// static
+Process Process::Open(ProcessId pid) {
+  return Process(pid);
+}
+
+// static
+Process Process::OpenWithExtraPrivileges(ProcessId pid) {
+  return Process(pid);
+}
+
+bool Process::IsValid() const {
+  return process_ != kNullProcessHandle;
+}
+
+ProcessHandle Process::Handle() const {
+  return process_;
+}
+
+Process Process::Duplicate() const {
+  return Process(process_);
+}
+
+ProcessHandle Process::Release() {
+  ProcessHandle handle = process_;
+  Close();
+  return handle;
+}
+
+ProcessId Process::Pid() const {
+  return process_;
+}
+
+Time Process::CreationTime() const {
+  return Time();
+}
+
+bool Process::is_current() const {
+  return Handle() == kCurrentProcessHandle;
+}
+
+void Process::Close() {
+  process_ = kNullProcessHandle;
+}
+
+bool Process::WaitForExit(int* exit_code) const {
+  return false;
+}
+
+bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
+  return false;
+}
+
+void Process::Exited(int exit_code) const {}
+
+bool Process::IsProcessBackgrounded() const {
+  return false;
+}
+
+bool Process::SetProcessBackgrounded(bool value) {
+  return false;
+}
+
+int Process::GetPriority() const {
+  return -1;
+}
+
+}  // namespace base
diff --git a/base/process/process_win.cc b/base/process/process_win.cc
index cdefa09..abbfa18 100644
--- a/base/process/process_win.cc
+++ b/base/process/process_win.cc
@@ -112,6 +112,12 @@
   return Process(out_handle);
 }
 
+ProcessHandle Process::Release() {
+  if (is_current())
+    return ::GetCurrentProcess();
+  return process_.Take();
+}
+
 ProcessId Process::Pid() const {
   DCHECK(IsValid());
   return GetProcId(Handle());
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
index fc26e93..01500199 100644
--- a/build/android/BUILD.gn
+++ b/build/android/BUILD.gn
@@ -52,9 +52,32 @@
     "devil_chromium.json",
     "//third_party/catapult/third_party/gsutil/",
     "//third_party/catapult/devil/devil/devil_dependencies.json",
+
+    # Read by gn_helpers.BuildWithChromium()
+    "//build/config/gclient_args.gni",
   ]
 }
 
+# Contains runtime deps for installing apks.
+# E.g. from test_runner.py or from apk_operations.py.
+group("apk_installer_data") {
+  # Other //build users let devil library fetch these from Google Storage.
+  if (build_with_chromium) {
+    data_deps = [
+      "//build/android/pylib/device/commands",
+      "//tools/android/md5sum",
+    ]
+    data = [
+      "//third_party/android_build_tools/bundletool/bundletool-all-1.2.0.jar",
+    ]
+  }
+}
+
+python_library("apk_operations_py") {
+  pydeps_file = "apk_operations.pydeps"
+  deps = [ ":apk_installer_data" ]
+}
+
 python_library("test_runner_py") {
   testonly = true
   pydeps_file = "test_runner.pydeps"
@@ -69,14 +92,17 @@
     "${android_sdk_root}/platform-tools/adb",
     "//third_party/requests/",
   ]
-  data_deps = [ ":devil_chromium_py" ]
+  data_deps = [
+    ":apk_installer_data",
+    ":devil_chromium_py",
+    ":logdog_wrapper_py",
+    ":stack_tools",
+  ]
+
+  # Other //build users let devil library fetch these from Google Storage.
   if (build_with_chromium) {
-    data += [
-      "//third_party/android_build_tools/bundletool/bundletool-all-1.2.0.jar",
-      "//tools/android/avd/proto/",
-    ]
-    data_deps +=
-        [ "//third_party/android_platform/development/scripts:stack_py" ]
+    data_deps += [ "//tools/android/forwarder2" ]
+    data += [ "//tools/android/avd/proto/" ]
     if (is_asan) {
       data_deps += [ "//tools/android/asan/third_party:asan_device_setup" ]
     }
@@ -104,19 +130,14 @@
   ]
 }
 
-python_library("bundle_wrapper_script_py") {
-  pydeps_file = "gyp/create_bundle_wrapper_script.pydeps"
-  data = [
-    "//third_party/android_build_tools/bundletool/bundletool-all-1.2.0.jar",
-  ]
-}
-
 # Tools necessary for symbolizing tombstones or stack traces that are output to
 # logcat.
 # Hidden behind build_with_chromium because some third party repos that use
 # //build don't pull in //third_party/android_platform.
-if (build_with_chromium) {
-  group("stack_tools") {
+# TODO(crbug.com/1120190): Move stack script into //build/third_party
+#     and enable unconditionally.
+group("stack_tools") {
+  if (build_with_chromium) {
     data = [
       "tombstones.py",
       "pylib/symbols/",
diff --git a/build/android/apk_operations.pydeps b/build/android/apk_operations.pydeps
new file mode 100644
index 0000000..3bbb036
--- /dev/null
+++ b/build/android/apk_operations.pydeps
@@ -0,0 +1,110 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android --output build/android/apk_operations.pydeps build/android/apk_operations.py
+../../third_party/catapult/common/py_utils/py_utils/__init__.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py
+../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
+../../third_party/catapult/common/py_utils/py_utils/lock.py
+../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
+../../third_party/catapult/dependency_manager/dependency_manager/__init__.py
+../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/base_config.py
+../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py
+../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py
+../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py
+../../third_party/catapult/dependency_manager/dependency_manager/manager.py
+../../third_party/catapult/dependency_manager/dependency_manager/uploader.py
+../../third_party/catapult/devil/devil/__init__.py
+../../third_party/catapult/devil/devil/android/__init__.py
+../../third_party/catapult/devil/devil/android/apk_helper.py
+../../third_party/catapult/devil/devil/android/constants/__init__.py
+../../third_party/catapult/devil/devil/android/constants/chrome.py
+../../third_party/catapult/devil/devil/android/constants/file_system.py
+../../third_party/catapult/devil/devil/android/decorators.py
+../../third_party/catapult/devil/devil/android/device_denylist.py
+../../third_party/catapult/devil/devil/android/device_errors.py
+../../third_party/catapult/devil/devil/android/device_signal.py
+../../third_party/catapult/devil/devil/android/device_temp_file.py
+../../third_party/catapult/devil/devil/android/device_utils.py
+../../third_party/catapult/devil/devil/android/flag_changer.py
+../../third_party/catapult/devil/devil/android/install_commands.py
+../../third_party/catapult/devil/devil/android/logcat_monitor.py
+../../third_party/catapult/devil/devil/android/md5sum.py
+../../third_party/catapult/devil/devil/android/ndk/__init__.py
+../../third_party/catapult/devil/devil/android/ndk/abis.py
+../../third_party/catapult/devil/devil/android/sdk/__init__.py
+../../third_party/catapult/devil/devil/android/sdk/aapt.py
+../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
+../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../third_party/catapult/devil/devil/android/sdk/bundletool.py
+../../third_party/catapult/devil/devil/android/sdk/intent.py
+../../third_party/catapult/devil/devil/android/sdk/keyevent.py
+../../third_party/catapult/devil/devil/android/sdk/split_select.py
+../../third_party/catapult/devil/devil/android/sdk/version_codes.py
+../../third_party/catapult/devil/devil/android/tools/__init__.py
+../../third_party/catapult/devil/devil/android/tools/script_common.py
+../../third_party/catapult/devil/devil/base_error.py
+../../third_party/catapult/devil/devil/constants/__init__.py
+../../third_party/catapult/devil/devil/constants/exit_codes.py
+../../third_party/catapult/devil/devil/devil_env.py
+../../third_party/catapult/devil/devil/utils/__init__.py
+../../third_party/catapult/devil/devil/utils/cmd_helper.py
+../../third_party/catapult/devil/devil/utils/host_utils.py
+../../third_party/catapult/devil/devil/utils/lazy/__init__.py
+../../third_party/catapult/devil/devil/utils/lazy/weak_constant.py
+../../third_party/catapult/devil/devil/utils/logging_common.py
+../../third_party/catapult/devil/devil/utils/lsusb.py
+../../third_party/catapult/devil/devil/utils/parallelizer.py
+../../third_party/catapult/devil/devil/utils/reraiser_thread.py
+../../third_party/catapult/devil/devil/utils/reset_usb.py
+../../third_party/catapult/devil/devil/utils/run_tests_helper.py
+../../third_party/catapult/devil/devil/utils/timeout_retry.py
+../../third_party/catapult/devil/devil/utils/watchdog_timer.py
+../../third_party/catapult/devil/devil/utils/zip_utils.py
+../../third_party/catapult/third_party/zipfile/zipfile_2_7_13.py
+../../third_party/jinja2/__init__.py
+../../third_party/jinja2/_compat.py
+../../third_party/jinja2/bccache.py
+../../third_party/jinja2/compiler.py
+../../third_party/jinja2/defaults.py
+../../third_party/jinja2/environment.py
+../../third_party/jinja2/exceptions.py
+../../third_party/jinja2/filters.py
+../../third_party/jinja2/idtracking.py
+../../third_party/jinja2/lexer.py
+../../third_party/jinja2/loaders.py
+../../third_party/jinja2/nodes.py
+../../third_party/jinja2/optimizer.py
+../../third_party/jinja2/parser.py
+../../third_party/jinja2/runtime.py
+../../third_party/jinja2/tests.py
+../../third_party/jinja2/utils.py
+../../third_party/jinja2/visitor.py
+../../third_party/markupsafe/__init__.py
+../../third_party/markupsafe/_compat.py
+../../third_party/markupsafe/_native.py
+../gn_helpers.py
+../print_python_deps.py
+adb_command_line.py
+apk_operations.py
+convert_dex_profile.py
+devil_chromium.py
+gyp/bundletool.py
+gyp/dex.py
+gyp/util/__init__.py
+gyp/util/build_utils.py
+gyp/util/md5_check.py
+gyp/util/resource_utils.py
+gyp/util/zipalign.py
+incremental_install/__init__.py
+incremental_install/installer.py
+pylib/__init__.py
+pylib/constants/__init__.py
+pylib/constants/host_paths.py
+pylib/symbols/__init__.py
+pylib/symbols/deobfuscator.py
+pylib/utils/__init__.py
+pylib/utils/app_bundle_utils.py
+pylib/utils/simpleperf.py
+pylib/utils/time_profile.py
diff --git a/build/android/devil_chromium.py b/build/android/devil_chromium.py
index 1cd5a871..20ae1e3 100644
--- a/build/android/devil_chromium.py
+++ b/build/android/devil_chromium.py
@@ -7,14 +7,21 @@
 import os
 import sys
 
+from pylib import constants
 from pylib.constants import host_paths
 
 if host_paths.DEVIL_PATH not in sys.path:
-  sys.path.append(host_paths.DEVIL_PATH)
+  sys.path.insert(1, host_paths.DEVIL_PATH)
 
 from devil import devil_env
 from devil.android.ndk import abis
 
+_BUILD_DIR = os.path.join(constants.DIR_SOURCE_ROOT, 'build')
+if _BUILD_DIR not in sys.path:
+  sys.path.insert(1, _BUILD_DIR)
+
+import gn_helpers
+
 _DEVIL_CONFIG = os.path.abspath(
     os.path.join(os.path.dirname(__file__), 'devil_chromium.json'))
 
@@ -107,6 +114,35 @@
 }
 
 
+def _UseLocalBuildProducts(output_directory, devil_dynamic_config):
+  output_directory = os.path.abspath(output_directory)
+  devil_dynamic_config['dependencies'] = {
+      dep_name: {
+          'file_info': {
+              '%s_%s' % (dep_config['platform'], dep_config['arch']): {
+                  'local_paths': [
+                      os.path.join(output_directory,
+                                   *dep_config['path_components']),
+                  ],
+              }
+              for dep_config in dep_configs
+          }
+      }
+      for dep_name, dep_configs in _DEVIL_BUILD_PRODUCT_DEPS.iteritems()
+  }
+
+
+def _BuildWithChromium():
+  """Returns value of gclient's |build_with_chromium|."""
+  gni_path = os.path.join(_BUILD_DIR, 'config', 'gclient_args.gni')
+  if not os.path.exists(gni_path):
+    return False
+  with open(gni_path) as f:
+    data = f.read()
+  args = gn_helpers.FromGNArgs(data)
+  return args.get('build_with_chromium', False)
+
+
 def Initialize(output_directory=None, custom_deps=None, adb_path=None):
   """Initializes devil with chromium's binaries and third-party libraries.
 
@@ -134,26 +170,18 @@
     adb_path: An optional path to use for the adb binary. If not set, this uses
       the adb binary provided by the Android SDK.
   """
+  build_with_chromium = _BuildWithChromium()
 
   devil_dynamic_config = {
     'config_type': 'BaseConfig',
     'dependencies': {},
   }
-  if output_directory:
-    output_directory = os.path.abspath(output_directory)
-    devil_dynamic_config['dependencies'] = {
-      dep_name: {
-        'file_info': {
-          '%s_%s' % (dep_config['platform'], dep_config['arch']): {
-            'local_paths': [
-              os.path.join(output_directory, *dep_config['path_components']),
-            ],
-          }
-          for dep_config in dep_configs
-        }
-      }
-      for dep_name, dep_configs in _DEVIL_BUILD_PRODUCT_DEPS.iteritems()
-    }
+  if build_with_chromium and output_directory:
+    # Non-chromium users of chromium's //build directory fetch build products
+    # from google storage rather than use locally built copies. Chromium uses
+    # locally-built copies so that changes to the tools can be easily tested.
+    _UseLocalBuildProducts(output_directory, devil_dynamic_config)
+
   if custom_deps:
     devil_dynamic_config['dependencies'].update(custom_deps)
   if adb_path:
@@ -167,5 +195,6 @@
       }
     })
 
-  devil_env.config.Initialize(
-      configs=[devil_dynamic_config], config_files=[_DEVIL_CONFIG])
+  config_files = [_DEVIL_CONFIG] if build_with_chromium else None
+  devil_env.config.Initialize(configs=[devil_dynamic_config],
+                              config_files=config_files)
diff --git a/build/android/devil_chromium.pydeps b/build/android/devil_chromium.pydeps
index ea8f0c2..4b9c58d 100644
--- a/build/android/devil_chromium.pydeps
+++ b/build/android/devil_chromium.pydeps
@@ -32,6 +32,7 @@
 ../../third_party/catapult/devil/devil/utils/timeout_retry.py
 ../../third_party/catapult/devil/devil/utils/watchdog_timer.py
 ../../third_party/catapult/third_party/zipfile/zipfile_2_7_13.py
+../gn_helpers.py
 devil_chromium.py
 pylib/__init__.py
 pylib/constants/__init__.py
diff --git a/build/android/gyp/create_bundle_wrapper_script.py b/build/android/gyp/create_bundle_wrapper_script.py
index 5f576ac..b28a6ab 100755
--- a/build/android/gyp/create_bundle_wrapper_script.py
+++ b/build/android/gyp/create_bundle_wrapper_script.py
@@ -10,11 +10,6 @@
 import string
 import sys
 
-# Import apk_operations even though this script doesn't use it so that
-# targets that depend on the wrapper scripts will rebuild when apk_operations
-# or its deps change.
-sys.path.insert(1, os.path.join(os.path.dirname(__file__), os.pardir))
-import apk_operations  # pylint: disable=unused-import
 from util import build_utils
 
 SCRIPT_TEMPLATE = string.Template("""\
diff --git a/build/android/gyp/create_bundle_wrapper_script.pydeps b/build/android/gyp/create_bundle_wrapper_script.pydeps
index 65222c6..7758ed6 100644
--- a/build/android/gyp/create_bundle_wrapper_script.pydeps
+++ b/build/android/gyp/create_bundle_wrapper_script.pydeps
@@ -1,111 +1,6 @@
 # Generated by running:
 #   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/create_bundle_wrapper_script.pydeps build/android/gyp/create_bundle_wrapper_script.py
-../../../third_party/catapult/common/py_utils/py_utils/__init__.py
-../../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py
-../../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
-../../../third_party/catapult/common/py_utils/py_utils/lock.py
-../../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
-../../../third_party/catapult/dependency_manager/dependency_manager/__init__.py
-../../../third_party/catapult/dependency_manager/dependency_manager/archive_info.py
-../../../third_party/catapult/dependency_manager/dependency_manager/base_config.py
-../../../third_party/catapult/dependency_manager/dependency_manager/cloud_storage_info.py
-../../../third_party/catapult/dependency_manager/dependency_manager/dependency_info.py
-../../../third_party/catapult/dependency_manager/dependency_manager/dependency_manager_util.py
-../../../third_party/catapult/dependency_manager/dependency_manager/exceptions.py
-../../../third_party/catapult/dependency_manager/dependency_manager/local_path_info.py
-../../../third_party/catapult/dependency_manager/dependency_manager/manager.py
-../../../third_party/catapult/dependency_manager/dependency_manager/uploader.py
-../../../third_party/catapult/devil/devil/__init__.py
-../../../third_party/catapult/devil/devil/android/__init__.py
-../../../third_party/catapult/devil/devil/android/apk_helper.py
-../../../third_party/catapult/devil/devil/android/constants/__init__.py
-../../../third_party/catapult/devil/devil/android/constants/chrome.py
-../../../third_party/catapult/devil/devil/android/constants/file_system.py
-../../../third_party/catapult/devil/devil/android/decorators.py
-../../../third_party/catapult/devil/devil/android/device_denylist.py
-../../../third_party/catapult/devil/devil/android/device_errors.py
-../../../third_party/catapult/devil/devil/android/device_signal.py
-../../../third_party/catapult/devil/devil/android/device_temp_file.py
-../../../third_party/catapult/devil/devil/android/device_utils.py
-../../../third_party/catapult/devil/devil/android/flag_changer.py
-../../../third_party/catapult/devil/devil/android/install_commands.py
-../../../third_party/catapult/devil/devil/android/logcat_monitor.py
-../../../third_party/catapult/devil/devil/android/md5sum.py
-../../../third_party/catapult/devil/devil/android/ndk/__init__.py
-../../../third_party/catapult/devil/devil/android/ndk/abis.py
-../../../third_party/catapult/devil/devil/android/sdk/__init__.py
-../../../third_party/catapult/devil/devil/android/sdk/aapt.py
-../../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
-../../../third_party/catapult/devil/devil/android/sdk/build_tools.py
-../../../third_party/catapult/devil/devil/android/sdk/bundletool.py
-../../../third_party/catapult/devil/devil/android/sdk/intent.py
-../../../third_party/catapult/devil/devil/android/sdk/keyevent.py
-../../../third_party/catapult/devil/devil/android/sdk/split_select.py
-../../../third_party/catapult/devil/devil/android/sdk/version_codes.py
-../../../third_party/catapult/devil/devil/android/tools/__init__.py
-../../../third_party/catapult/devil/devil/android/tools/script_common.py
-../../../third_party/catapult/devil/devil/base_error.py
-../../../third_party/catapult/devil/devil/constants/__init__.py
-../../../third_party/catapult/devil/devil/constants/exit_codes.py
-../../../third_party/catapult/devil/devil/devil_env.py
-../../../third_party/catapult/devil/devil/utils/__init__.py
-../../../third_party/catapult/devil/devil/utils/cmd_helper.py
-../../../third_party/catapult/devil/devil/utils/host_utils.py
-../../../third_party/catapult/devil/devil/utils/lazy/__init__.py
-../../../third_party/catapult/devil/devil/utils/lazy/weak_constant.py
-../../../third_party/catapult/devil/devil/utils/logging_common.py
-../../../third_party/catapult/devil/devil/utils/lsusb.py
-../../../third_party/catapult/devil/devil/utils/parallelizer.py
-../../../third_party/catapult/devil/devil/utils/reraiser_thread.py
-../../../third_party/catapult/devil/devil/utils/reset_usb.py
-../../../third_party/catapult/devil/devil/utils/run_tests_helper.py
-../../../third_party/catapult/devil/devil/utils/timeout_retry.py
-../../../third_party/catapult/devil/devil/utils/watchdog_timer.py
-../../../third_party/catapult/devil/devil/utils/zip_utils.py
-../../../third_party/catapult/third_party/zipfile/zipfile_2_7_13.py
-../../../third_party/jinja2/__init__.py
-../../../third_party/jinja2/_compat.py
-../../../third_party/jinja2/bccache.py
-../../../third_party/jinja2/compiler.py
-../../../third_party/jinja2/defaults.py
-../../../third_party/jinja2/environment.py
-../../../third_party/jinja2/exceptions.py
-../../../third_party/jinja2/filters.py
-../../../third_party/jinja2/idtracking.py
-../../../third_party/jinja2/lexer.py
-../../../third_party/jinja2/loaders.py
-../../../third_party/jinja2/nodes.py
-../../../third_party/jinja2/optimizer.py
-../../../third_party/jinja2/parser.py
-../../../third_party/jinja2/runtime.py
-../../../third_party/jinja2/tests.py
-../../../third_party/jinja2/utils.py
-../../../third_party/jinja2/visitor.py
-../../../third_party/markupsafe/__init__.py
-../../../third_party/markupsafe/_compat.py
-../../../third_party/markupsafe/_native.py
 ../../gn_helpers.py
-../../print_python_deps.py
-../adb_command_line.py
-../apk_operations.py
-../convert_dex_profile.py
-../devil_chromium.py
-../incremental_install/__init__.py
-../incremental_install/installer.py
-../pylib/__init__.py
-../pylib/constants/__init__.py
-../pylib/constants/host_paths.py
-../pylib/symbols/__init__.py
-../pylib/symbols/deobfuscator.py
-../pylib/utils/__init__.py
-../pylib/utils/app_bundle_utils.py
-../pylib/utils/simpleperf.py
-../pylib/utils/time_profile.py
-bundletool.py
 create_bundle_wrapper_script.py
-dex.py
 util/__init__.py
 util/build_utils.py
-util/md5_check.py
-util/resource_utils.py
-util/zipalign.py
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index f79f26ef..15707eb 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -713,7 +713,7 @@
                                "deps",
                                "public_deps",
                              ])
-      data_deps = [ "//tools/android/md5sum" ]
+      data_deps = []
       if (defined(invoker.data_deps)) {
         data_deps += invoker.data_deps
       }
@@ -739,18 +739,6 @@
 
   generate_android_wrapper(target_name) {
     wrapper_script = "$root_build_dir/bin/run_${_test_name}"
-    forward_variables_from(invoker,
-                           [
-                             "data_deps",
-                             "deps",
-                           ])
-    if (!defined(deps)) {
-      deps = []
-    }
-
-    if (!defined(data_deps)) {
-      data_deps = []
-    }
 
     executable = "//testing/test_env.py"
 
@@ -764,11 +752,14 @@
     if (defined(invoker.deps)) {
       deps = invoker.deps
     }
-    data_deps = []
+    data_deps = [ "//build/android:test_runner_py" ]
     if (defined(invoker.data_deps)) {
-      data_deps = invoker.data_deps
+      data_deps += invoker.data_deps
     }
     data = [ "//testing/test_env.py" ]
+    if (defined(invoker.data)) {
+      data += invoker.data
+    }
 
     executable_args = [
       "@WrappedPath(" + rebase_path(_runner_script, root_build_dir) + ")",
@@ -776,10 +767,6 @@
       "--output-directory",
       "@WrappedPath(.)",
     ]
-    data_deps += [
-      "//build/android:logdog_wrapper_py",
-      "//build/android:test_runner_py",
-    ]
 
     if (_runtime_deps) {
       deps += [ ":$_runtime_deps_target" ]
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index effb1b9..b7dbc7b 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -3316,7 +3316,6 @@
         _generated_script = "$root_build_dir/bin/${invoker.target_name}"
         script = "//build/android/gyp/create_apk_operations_script.py"
         outputs = [ _generated_script ]
-        data_deps = [ "//tools/android/md5sum" ]
         args = [
           "--script-output-path",
           rebase_path(_generated_script, root_build_dir),
@@ -3350,9 +3349,12 @@
             _static_library_apk_path,
           ]
         }
-        if (!defined(data)) {
-          data = []
-        }
+        data = []
+        data_deps = [
+          "//build/android:apk_operations_py",
+          "//build/android:stack_tools",
+        ]
+
         if (_proguard_enabled && !_incremental_apk) {
           # Required by logcat command.
           data_deps += [ "//build/android/stacktrace:java_deobfuscate" ]
@@ -3419,10 +3421,6 @@
         data_deps += [ ":${target_name}__lint" ]
       }
 
-      if (_incremental_apk) {
-        # device/commands is used by the installer script to push files via .zip.
-        data_deps += [ "//build/android/pylib/device/commands" ]
-      }
       if (_uses_static_library) {
         data_deps += [ invoker.static_library_provider ]
       }
@@ -3791,8 +3789,6 @@
         # symbolization can be done.
         ":${target_name}__secondary_abi_shared_library_list",
         ":${target_name}__shared_library_list",
-        "//build/android/pylib/device/commands",
-        "//tools/android/forwarder2",
       ]
       if (defined(invoker.data_deps)) {
         data_deps += invoker.data_deps
@@ -3970,10 +3966,6 @@
         "//base:base_java",
         "//testing/android/reporter:reporter_java",
       ]
-      data_deps += [ "//build/android/pylib/device/commands" ]
-      if (host_os == "linux") {
-        data_deps += [ "//tools/android/forwarder2" ]
-      }
     }
   }
 
@@ -5006,8 +4998,8 @@
         _bundle_path,
       ]
       data_deps = [
-        "//build/android:bundle_wrapper_script_py",
-        "//tools/android/md5sum",
+        "//build/android:apk_operations_py",
+        "//build/android:stack_tools",
       ]
 
       deps = [ _base_module_build_config_target ]
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 6c69653..ce06cb8 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -192,6 +192,8 @@
     "metrics/ukm_smoothness_data.h",
     "metrics/video_playback_roughness_reporter.cc",
     "metrics/video_playback_roughness_reporter.h",
+    "metrics/web_vital_metrics.cc",
+    "metrics/web_vital_metrics.h",
     "raster/bitmap_raster_buffer_provider.cc",
     "raster/bitmap_raster_buffer_provider.h",
     "raster/gpu_raster_buffer_provider.cc",
diff --git a/cc/layers/heads_up_display_layer.cc b/cc/layers/heads_up_display_layer.cc
index b1df267..01639af 100644
--- a/cc/layers/heads_up_display_layer.cc
+++ b/cc/layers/heads_up_display_layer.cc
@@ -5,6 +5,7 @@
 #include "cc/layers/heads_up_display_layer.h"
 
 #include <algorithm>
+#include <utility>
 #include <vector>
 
 #include "base/trace_event/trace_event.h"
@@ -68,6 +69,11 @@
   layout_shift_rects_ = rects;
 }
 
+void HeadsUpDisplayLayer::UpdateWebVitalMetrics(
+    std::unique_ptr<WebVitalMetrics> web_vital_metrics) {
+  web_vital_metrics_ = std::move(web_vital_metrics);
+}
+
 void HeadsUpDisplayLayer::PushPropertiesTo(LayerImpl* layer) {
   Layer::PushPropertiesTo(layer);
   TRACE_EVENT0("cc", "HeadsUpDisplayLayer::PushPropertiesTo");
@@ -77,6 +83,8 @@
   layer_impl->SetHUDTypeface(typeface_);
   layer_impl->SetLayoutShiftRects(layout_shift_rects_);
   layout_shift_rects_.clear();
+  if (web_vital_metrics_)
+    layer_impl->SetWebVitalMetrics(std::move(web_vital_metrics_));
 }
 
 }  // namespace cc
diff --git a/cc/layers/heads_up_display_layer.h b/cc/layers/heads_up_display_layer.h
index 1dea4e9..9d497439 100644
--- a/cc/layers/heads_up_display_layer.h
+++ b/cc/layers/heads_up_display_layer.h
@@ -7,9 +7,11 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "cc/cc_export.h"
 #include "cc/layers/layer.h"
+#include "cc/metrics/web_vital_metrics.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/core/SkTypeface.h"
 #include "ui/gfx/geometry/rect.h"
@@ -25,6 +27,8 @@
 
   void UpdateLocationAndSize(const gfx::Size& device_viewport,
                              float device_scale_factor);
+  void UpdateWebVitalMetrics(
+      std::unique_ptr<WebVitalMetrics> web_vital_metrics);
 
   const std::vector<gfx::Rect>& LayoutShiftRects() const;
   void SetLayoutShiftRects(const std::vector<gfx::Rect>& rects);
@@ -43,6 +47,8 @@
 
   sk_sp<SkTypeface> typeface_;
   std::vector<gfx::Rect> layout_shift_rects_;
+
+  std::unique_ptr<WebVitalMetrics> web_vital_metrics_;
 };
 
 }  // namespace cc
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index 1c01564..2a4acbb 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include <algorithm>
+#include <iomanip>
 #include <utility>
 #include <vector>
 
@@ -90,6 +91,12 @@
   }
 };
 
+std::string ToStringTwoDecimalPrecision(double input) {
+  std::stringstream stream;
+  stream << std::fixed << std::setprecision(2) << input;
+  return stream.str();
+}
+
 }  // namespace
 
 HeadsUpDisplayLayerImpl::HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl,
@@ -522,6 +529,11 @@
   layout_shift_rects_ = rects;
 }
 
+void HeadsUpDisplayLayerImpl::SetWebVitalMetrics(
+    std::unique_ptr<WebVitalMetrics> web_vital_metrics) {
+  web_vital_metrics_ = std::move(web_vital_metrics);
+}
+
 void HeadsUpDisplayLayerImpl::PushPropertiesTo(LayerImpl* layer) {
   LayerImpl::PushPropertiesTo(layer);
 
@@ -531,6 +543,7 @@
   layer_impl->SetHUDTypeface(typeface_);
   layer_impl->SetLayoutShiftRects(layout_shift_rects_);
   layout_shift_rects_.clear();
+  layer_impl->SetWebVitalMetrics(std::move(web_vital_metrics_));
 }
 
 void HeadsUpDisplayLayerImpl::UpdateHudContents() {
@@ -1036,14 +1049,40 @@
                                                     int right,
                                                     int top,
                                                     int width) const {
-  std::string loading_status = "-";
-  // TODO(weiliangc): Update loading status with actual number.
+  std::string largest_contentful_paint = "-";
+  SkColor largest_contentful_paint_color = DebugColors::HUDTitleColor();
+  if (web_vital_metrics_) {
+    if (web_vital_metrics_->largest_contentful_paint.has_value()) {
+      double time = web_vital_metrics_->largest_contentful_paint->InSecondsF();
+      largest_contentful_paint = ToStringTwoDecimalPrecision(time) + " s";
+      if (time < 2.5f)
+        largest_contentful_paint_color = SK_ColorGREEN;
+      else if (time < 4.f)
+        largest_contentful_paint_color = SK_ColorYELLOW;
+      else
+        largest_contentful_paint_color = SK_ColorRED;
+    }
+  }
+  std::string first_input_delay = "-";
+  SkColor first_input_delay_color = DebugColors::HUDTitleColor();
+  if (web_vital_metrics_) {
+    if (web_vital_metrics_->first_input_delay.has_value()) {
+      double delay = web_vital_metrics_->first_input_delay->InMillisecondsF();
+      first_input_delay = ToStringTwoDecimalPrecision(delay) + " ms";
+      if (delay < 100.f)
+        first_input_delay_color = SK_ColorGREEN;
+      else if (delay < 300.f)
+        first_input_delay_color = SK_ColorYELLOW;
+      else
+        first_input_delay_color = SK_ColorRED;
+    }
+  }
 
   const int kPadding = 4;
   const int kTitleFontHeight = 13;
   const int kFontHeight = 12;
 
-  const int height = kTitleFontHeight + kFontHeight + 3 * kPadding;
+  const int height = 2 * kTitleFontHeight + 2 * kFontHeight + 5 * kPadding;
   const int left = 0;
   const SkRect area = SkRect::MakeXYWH(left, top, width, height);
 
@@ -1053,9 +1092,20 @@
   SkPoint metrics_pos = SkPoint::Make(left + width - kPadding,
                                       top + 2 * kFontHeight + 2 * kPadding);
   flags.setColor(DebugColors::HUDTitleColor());
-  DrawText(canvas, flags, "Load time", TextAlign::kLeft, kTitleFontHeight,
-           left + kPadding, top + kFontHeight + kPadding);
-  DrawText(canvas, flags, loading_status, TextAlign::kRight, kFontHeight,
+  DrawText(canvas, flags, "Largest contentful paint", TextAlign::kLeft,
+           kTitleFontHeight, left + kPadding, top + kFontHeight + kPadding);
+  flags.setColor(largest_contentful_paint_color);
+  DrawText(canvas, flags, largest_contentful_paint, TextAlign::kRight,
+           kFontHeight, metrics_pos);
+
+  metrics_pos = SkPoint::Make(left + width - kPadding,
+                              top + 4 * kFontHeight + 4 * kPadding);
+  flags.setColor(DebugColors::HUDTitleColor());
+  DrawText(canvas, flags, "First input delay:", TextAlign::kLeft,
+           kTitleFontHeight, left + kPadding,
+           top + 3 * kFontHeight + 3 * kPadding);
+  flags.setColor(first_input_delay_color);
+  DrawText(canvas, flags, first_input_delay, TextAlign::kRight, kFontHeight,
            metrics_pos);
 
   return area;
diff --git a/cc/layers/heads_up_display_layer_impl.h b/cc/layers/heads_up_display_layer_impl.h
index 53ce0a9..c98b60199 100644
--- a/cc/layers/heads_up_display_layer_impl.h
+++ b/cc/layers/heads_up_display_layer_impl.h
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "cc/cc_export.h"
 #include "cc/layers/layer_impl.h"
+#include "cc/metrics/web_vital_metrics.h"
 #include "cc/resources/memory_history.h"
 #include "cc/resources/resource_pool.h"
 #include "cc/trees/debug_rect_history.h"
@@ -68,6 +69,7 @@
   void SetHUDTypeface(sk_sp<SkTypeface> typeface);
   void SetLayoutShiftRects(const std::vector<gfx::Rect>& rects);
   const std::vector<gfx::Rect>& LayoutShiftRects() const;
+  void SetWebVitalMetrics(std::unique_ptr<WebVitalMetrics> web_vital_metrics);
 
   // This evicts hud quad appended during render pass preparation.
   void EvictHudQuad(const viz::CompositorRenderPassList& list);
@@ -151,6 +153,8 @@
   std::vector<DebugRect> paint_rects_;
   std::vector<DebugRect> layout_shift_debug_rects_;
 
+  std::unique_ptr<WebVitalMetrics> web_vital_metrics_;
+
   base::TimeTicks time_of_last_graph_update_;
 };
 
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index 772b0e5..aca2eaf 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -505,17 +505,15 @@
 
 SkColor Layer::SafeOpaqueBackgroundColor() const {
   if (contents_opaque()) {
-    DCHECK_EQ(SkColorGetA(safe_opaque_background_color_), SK_AlphaOPAQUE);
+    // TODO(936906): We should uncomment this DCHECK, since the
+    // |safe_opaque_background_color_| could be transparent if it is never set
+    // (the default is 0). But to do that, one test needs to be fixed.
+    // DCHECK_EQ(SkColorGetA(safe_opaque_background_color_), SK_AlphaOPAQUE);
     return safe_opaque_background_color_;
   }
   SkColor color = background_color();
-  if (SkColorGetA(color) == SK_AlphaOPAQUE) {
-    // The layer is not opaque while the background color is, meaning that the
-    // background color doesn't cover the whole layer. Use SK_ColorTRANSPARENT
-    // to avoid intrusive checkerboard where the layer is not covered by the
-    // background color.
+  if (SkColorGetA(color) == 255)
     color = SK_ColorTRANSPARENT;
-  }
   return color;
 }
 
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index c21d007..6991597 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -161,8 +161,7 @@
   // SetSafeOpaqueBackgroundColor() which should be an opaque color. Otherwise,
   // it returns something non-opaque. It prefers to return the
   // background_color(), but if the background_color() is opaque (and this layer
-  // claims to not be), then SK_ColorTRANSPARENT is returned to avoid intrusive
-  // checkerboard where the layer is not covered by the background_color().
+  // claims to not be), then SK_ColorTRANSPARENT is returned.
   SkColor SafeOpaqueBackgroundColor() const;
   // For testing, return the actual stored value.
   SkColor ActualSafeOpaqueBackgroundColorForTesting() const {
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index 920a3f5..460019a 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -555,6 +555,20 @@
   safe_opaque_background_color_ = background_color;
 }
 
+SkColor LayerImpl::SafeOpaqueBackgroundColor() const {
+  if (contents_opaque()) {
+    // TODO(936906): We should uncomment this DCHECK, since the
+    // |safe_opaque_background_color_| could be transparent if it is never set
+    // (the default is 0). But to do that, one test needs to be fixed.
+    // DCHECK_EQ(SkColorGetA(safe_opaque_background_color_), SK_AlphaOPAQUE);
+    return safe_opaque_background_color_;
+  }
+  SkColor color = background_color();
+  if (SkColorGetA(color) == 255)
+    color = SK_ColorTRANSPARENT;
+  return color;
+}
+
 void LayerImpl::SetContentsOpaque(bool opaque) {
   contents_opaque_ = opaque;
   contents_opaque_for_text_ = opaque;
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index dca1b33..6af8b62 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -163,11 +163,9 @@
   void SetBackgroundColor(SkColor background_color);
   SkColor background_color() const { return background_color_; }
   void SetSafeOpaqueBackgroundColor(SkColor background_color);
-  SkColor safe_opaque_background_color() const {
-    DCHECK(!contents_opaque() ||
-           SkColorGetA(safe_opaque_background_color_) == SK_AlphaOPAQUE);
-    return safe_opaque_background_color_;
-  }
+  // If contents_opaque(), return an opaque color else return a
+  // non-opaque color.  Tries to return background_color(), if possible.
+  SkColor SafeOpaqueBackgroundColor() const;
 
   // See Layer::SetContentsOpaque() and SetContentsOpaqueForText() for the
   // relationship between the two flags.
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index 2f6a867..c26d781 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -511,7 +511,7 @@
 
     if (!has_draw_quad) {
       // Checkerboard.
-      SkColor color = safe_opaque_background_color();
+      SkColor color = SafeOpaqueBackgroundColor();
       if (ShowDebugBorders(DebugBorderType::LAYER)) {
         // Fill the whole tile with the missing tile color.
         color = DebugColors::DefaultCheckerboardColor();
diff --git a/cc/metrics/web_vital_metrics.cc b/cc/metrics/web_vital_metrics.cc
new file mode 100644
index 0000000..d27f6b3
--- /dev/null
+++ b/cc/metrics/web_vital_metrics.cc
@@ -0,0 +1,23 @@
+// 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 "cc/metrics/web_vital_metrics.h"
+
+namespace cc {
+
+WebVitalMetrics::WebVitalMetrics() = default;
+
+WebVitalMetrics::WebVitalMetrics(const WebVitalMetrics& other) = default;
+
+bool WebVitalMetrics::HasValue() const {
+  if (largest_contentful_paint.has_value())
+    return true;
+
+  if (first_input_delay.has_value())
+    return true;
+
+  return false;
+}
+
+}  // namespace cc
diff --git a/cc/metrics/web_vital_metrics.h b/cc/metrics/web_vital_metrics.h
new file mode 100644
index 0000000..f8383b6
--- /dev/null
+++ b/cc/metrics/web_vital_metrics.h
@@ -0,0 +1,26 @@
+// 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 CC_METRICS_WEB_VITAL_METRICS_H_
+#define CC_METRICS_WEB_VITAL_METRICS_H_
+
+#include "base/time/time.h"
+#include "cc/cc_export.h"
+
+namespace cc {
+
+// Web Vital metrics reported from blink to be displayed with cc's HUD display.
+struct CC_EXPORT WebVitalMetrics {
+  base::Optional<base::TimeDelta> largest_contentful_paint;
+  base::Optional<base::TimeDelta> first_input_delay;
+
+  WebVitalMetrics();
+  WebVitalMetrics(const WebVitalMetrics& other);
+
+  bool HasValue() const;
+};
+
+}  // namespace cc
+
+#endif  // CC_METRICS_WEB_VITAL_METRICS_H_
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 037fd3d..1e22e6d 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -418,6 +418,9 @@
   std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() override {
     return nullptr;
   }
+  std::unique_ptr<WebVitalMetrics> GetWebVitalMetrics() override {
+    return nullptr;
+  }
   void NotifyThroughputTrackerResults(CustomTrackerResults results) override {
     test_hooks_->NotifyThroughputTrackerResults(std::move(results));
   }
diff --git a/cc/test/stub_layer_tree_host_client.cc b/cc/test/stub_layer_tree_host_client.cc
index f7bd15e..00464d2 100644
--- a/cc/test/stub_layer_tree_host_client.cc
+++ b/cc/test/stub_layer_tree_host_client.cc
@@ -5,6 +5,7 @@
 #include "cc/test/stub_layer_tree_host_client.h"
 
 #include "cc/metrics/begin_main_frame_metrics.h"
+#include "cc/metrics/web_vital_metrics.h"
 
 namespace cc {
 
@@ -15,4 +16,8 @@
   return nullptr;
 }
 
+std::unique_ptr<WebVitalMetrics> StubLayerTreeHostClient::GetWebVitalMetrics() {
+  return nullptr;
+}
+
 }  // namespace cc
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index 58e5529..3964f971 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_STUB_LAYER_TREE_HOST_CLIENT_H_
 #define CC_TEST_STUB_LAYER_TREE_HOST_CLIENT_H_
 
+#include <memory>
+
 #include "cc/paint/element_id.h"
 #include "cc/trees/layer_tree_host_client.h"
 
@@ -26,6 +28,7 @@
   void RecordEndOfFrameMetrics(base::TimeTicks,
                                ActiveFrameSequenceTrackers) override {}
   std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() override;
+  std::unique_ptr<WebVitalMetrics> GetWebVitalMetrics() override;
   void NotifyThroughputTrackerResults(CustomTrackerResults results) override {}
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
diff --git a/cc/test/test_layer_tree_host_base.cc b/cc/test/test_layer_tree_host_base.cc
index 26b5ef5..96b1b7a 100644
--- a/cc/test/test_layer_tree_host_base.cc
+++ b/cc/test/test_layer_tree_host_base.cc
@@ -123,7 +123,6 @@
     pending_layer_->SetDrawsContent(true);
     // LCD-text tests require the layer to be initially opaque.
     pending_layer_->SetContentsOpaque(true);
-    pending_layer_->SetSafeOpaqueBackgroundColor(SK_ColorWHITE);
 
     pending_tree->SetElementIdsForTesting();
     SetupRootProperties(pending_root);
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 1cd8d4e..6ffb53a 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -1522,6 +1522,16 @@
       root_layer_->AddChild(hud_layer_);
     hud_layer_->UpdateLocationAndSize(device_viewport_rect_.size(),
                                       device_scale_factor_);
+    if (debug_state_.show_web_vital_metrics) {
+      // This WebVitalMetrics is filled by the main frame, which is equivalent
+      // to WebPerf's numbers. The main frame's number doesn't include any
+      // iframes. UMA/UKM records metrics for the entire page aggregating all
+      // the frames. The page metrics are in the browser process.
+      // TODO(weiliangc): Get the page metrics for display.
+      auto metrics = client_->GetWebVitalMetrics();
+      if (metrics && metrics->HasValue())
+        hud_layer_->UpdateWebVitalMetrics(std::move(metrics));
+    }
   } else if (hud_layer_.get()) {
     hud_layer_->RemoveFromParent();
     hud_layer_ = nullptr;
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index e5e66fa..ea6852e 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -39,6 +39,7 @@
 #include "cc/metrics/begin_main_frame_metrics.h"
 #include "cc/metrics/events_metrics_manager.h"
 #include "cc/metrics/frame_sequence_tracker.h"
+#include "cc/metrics/web_vital_metrics.h"
 #include "cc/paint/node_id.h"
 #include "cc/trees/browser_controls_params.h"
 #include "cc/trees/compositor_mode.h"
diff --git a/cc/trees/layer_tree_host_client.cc b/cc/trees/layer_tree_host_client.cc
new file mode 100644
index 0000000..6baef66
--- /dev/null
+++ b/cc/trees/layer_tree_host_client.cc
@@ -0,0 +1,15 @@
+// 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 "cc/trees/layer_tree_host_client.h"
+
+#include "cc/metrics/web_vital_metrics.h"
+
+namespace cc {
+
+std::unique_ptr<WebVitalMetrics> LayerTreeHostClient::GetWebVitalMetrics() {
+  return nullptr;
+}
+
+}  // namespace cc
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index 5a804d2..562f9d0 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -25,6 +25,7 @@
 namespace cc {
 struct BeginMainFrameMetrics;
 struct ElementId;
+struct WebVitalMetrics;
 
 struct ApplyViewportChangesArgs {
   // Scroll offset delta of the inner (visual) viewport.
@@ -182,6 +183,9 @@
   virtual std::unique_ptr<BeginMainFrameMetrics> GetBeginMainFrameMetrics() = 0;
   virtual void NotifyThroughputTrackerResults(CustomTrackerResults results) = 0;
 
+  // Should only be implemented by Blink.
+  virtual std::unique_ptr<WebVitalMetrics> GetWebVitalMetrics() = 0;
+
   virtual void RunPaintBenchmark(int repeat_count,
                                  PaintBenchmarkResult& result) {}
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 4124c46..eaf42f5 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -6849,7 +6849,6 @@
                                                     std::move(recording));
     layer->SetBounds(gfx::Size(500, 500));
     layer->SetContentsOpaque(true);
-    layer->SetSafeOpaqueBackgroundColor(SK_ColorWHITE);
     // Avoid LCD text on the layer so we don't cause extra commits when we
     // pinch.
     CopyProperties(layer_tree_host()->InnerViewportScrollLayerForTesting(),
@@ -7155,7 +7154,6 @@
                                                     std::move(recording));
     layer->SetBounds(gfx::Size(500, 500));
     layer->SetContentsOpaque(true);
-    layer->SetSafeOpaqueBackgroundColor(SK_ColorWHITE);
     CopyProperties(layer_tree_host()->InnerViewportScrollLayerForTesting(),
                    layer.get());
     root->AddChild(layer);
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 03f1be2..9b115fd 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2147,6 +2147,8 @@
     "//components/minidump_uploader:minidump_uploader_java",
     "//components/module_installer/android:module_installer_java",
     "//third_party/android_deps:androidx_annotation_annotation_java",
+    "//third_party/android_deps:androidx_collection_collection_java",
+    "//third_party/android_deps:androidx_fragment_fragment_java",
     "//ui/android:ui_no_recycler_view_java",
 
     # Deps needed for child processes.
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 0a43519..93df9c7 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -707,6 +707,7 @@
   "java/res/drawable/logo_avatar_anonymous.xml",
   "java/res/drawable/logo_partly_cloudy.xml",
   "java/res/drawable/logo_translate_round.xml",
+  "java/res/drawable/long_screenshot.xml",
   "java/res/drawable/material_tooltip_background.xml",
   "java/res/drawable/mir_card.xml",
   "java/res/drawable/modern_toolbar_text_box_background_with_primary_color.xml",
diff --git a/chrome/android/expectations/lint-baseline.xml b/chrome/android/expectations/lint-baseline.xml
index da8e828..c810c5a 100644
--- a/chrome/android/expectations/lint-baseline.xml
+++ b/chrome/android/expectations/lint-baseline.xml
@@ -81,7 +81,7 @@
     <issue
         id="VisibleForTests"
         message="This method should only be accessed from tests or within private scope"
-        errorLine1="                IconProvider.getIcon(context, R.drawable.permission_location),"
+        errorLine1="                IconProvider.getIcon(context, R.drawable.ic_permission_location_outline),"
         errorLine2="                             ~~~~~~~">
         <location
             file="../../chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetCoordinator.java"
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetCoordinator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetCoordinator.java
index 4b3af37..f95beb3 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetCoordinator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetCoordinator.java
@@ -36,7 +36,7 @@
     public AddressAccessorySheetCoordinator(
             Context context, @Nullable RecyclerView.OnScrollListener scrollListener) {
         super(context.getString(R.string.address_accessory_sheet_title),
-                IconProvider.getIcon(context, R.drawable.permission_location),
+                IconProvider.getIcon(context, R.drawable.ic_permission_location_outline),
                 context.getString(R.string.address_accessory_sheet_toggle),
                 context.getString(R.string.address_accessory_sheet_opened),
                 R.layout.address_accessory_sheet, AccessoryTabType.ADDRESSES, scrollListener);
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
index 2f20130..02dd3f2b 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedProcessScopeDependencyProvider.java
@@ -12,6 +12,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.chrome.browser.base.SplitCompatUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.IdentityServicesProvider;
 import org.chromium.chrome.browser.xsurface.ImageFetchClient;
@@ -108,9 +109,6 @@
     }
 
     public static Context createFeedContext(Context context) {
-        if (!BundleUtils.isIsolatedSplitInstalled(context, FEED_SPLIT_NAME)) {
-            return context;
-        }
-        return BundleUtils.createIsolatedSplitContext(context, FEED_SPLIT_NAME);
+        return SplitCompatUtils.createContextForInflation(context, FEED_SPLIT_NAME);
     }
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
index f15c8f14..d0861e4 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
@@ -30,7 +30,6 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
-import org.chromium.chrome.browser.base.SplitCompatUtils;
 import org.chromium.chrome.browser.feed.shared.ScrollTracker;
 import org.chromium.chrome.browser.feed.shared.stream.Stream.ContentChangedListener;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
@@ -87,8 +86,6 @@
 public class FeedStreamSurface implements SurfaceActionsHandler, FeedActionsHandler {
     private static final String TAG = "FeedStreamSurface";
 
-    private static final String FEED_SPLIT_NAME = "feedv2";
-
     private static final int SNACKBAR_DURATION_MS_SHORT = 4000;
     private static final int SNACKBAR_DURATION_MS_LONG = 10000;
 
@@ -1019,10 +1016,6 @@
         mIsPlaceholderShown = false;
     }
 
-    private static Context createFeedContext(Context context) {
-        return SplitCompatUtils.createContextForInflation(context, FEED_SPLIT_NAME);
-    }
-
     // Detects animation finishes in RecyclerView.
     // https://stackoverflow.com/questions/33710605/detect-animation-finish-in-androids-recyclerview
     private class RecyclerViewAnimationFinishDetector implements ItemAnimatorFinishedListener {
diff --git a/chrome/android/feed/merging.md b/chrome/android/feed/merging.md
index 84977294..a7306bf 100644
--- a/chrome/android/feed/merging.md
+++ b/chrome/android/feed/merging.md
@@ -8,4 +8,4 @@
 The hash below represents the last commit from that repo that was reviewed for
 the potential need to merge here.
 
-Last checked commit ID: beb9bad469adb0bac21611269c728ecfa3396dd6
+Last checked commit ID: 77405b1370937094ae69af4933f3e06109dd53ba
diff --git a/chrome/android/java/res/drawable/long_screenshot.xml b/chrome/android/java/res/drawable/long_screenshot.xml
new file mode 100644
index 0000000..11a3f89
--- /dev/null
+++ b/chrome/android/java/res/drawable/long_screenshot.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M10.1,2L5.908,2C5.1304,2 4.5,2.6304 4.5,3.408L4.5,10L4.5,10"
+      android:strokeWidth="1.8"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/default_icon_color"
+      android:strokeLineCap="square"/>
+  <path
+      android:pathData="M14.2,2L18.392,2C19.1696,2 19.8,2.6304 19.8,3.408L19.8,10L19.8,10"
+      android:strokeWidth="1.8"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/default_icon_color"
+      android:strokeLineCap="square"/>
+  <path
+      android:pathData="M10.1,22L5.908,22C5.1304,22 4.5,21.3696 4.5,20.592L4.5,14L4.5,14"
+      android:strokeWidth="1.8"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/default_icon_color"
+      android:strokeLineCap="square"/>
+  <path
+      android:pathData="M14.2,22L18.392,22C19.1696,22 19.8,21.3696 19.8,20.592L19.8,14L19.8,14"
+      android:strokeWidth="1.8"
+      android:fillColor="#00000000"
+      android:strokeColor="@color/default_icon_color"
+      android:strokeLineCap="square"/>
+</vector>
diff --git a/chrome/android/java/res/layout/share_sheet_item.xml b/chrome/android/java/res/layout/share_sheet_item.xml
index f9296f8..c82cc8a 100644
--- a/chrome/android/java/res/layout/share_sheet_item.xml
+++ b/chrome/android/java/res/layout/share_sheet_item.xml
@@ -7,18 +7,18 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:clickable="true"
+    android:focusable="true"
     android:textAppearance="@style/TextAppearance.TextSmall.Primary"
     android:layout_height="fill_parent"
-    android:layout_width="68sp"
+    android:layout_width="69sp"
     android:layout_marginStart="8dp"
     android:layout_marginEnd="8dp"
-    android:layout_marginTop="16dp"
+    android:paddingTop="28dp"
     android:background="?attr/selectableItemBackground"
     android:gravity="center_horizontal"
     android:orientation="vertical" >
     <ImageView android:id="@+id/icon"
         android:layout_height="24dp"
-        android:layout_marginTop="12dp"
         android:layout_width="24dp"
         android:scaleType="fitCenter"
         tools:ignore="ContentDescription" />
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index b5c5e08..9867824 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -553,4 +553,6 @@
     <dimen name="sharing_hub_preview_icon_rounded_corner_radius">4dp</dimen>
     <dimen name="sharing_hub_preview_icon_size">48dp</dimen>
     <dimen name="sharing_hub_preview_inner_icon_size">24dp</dimen>
+    <dimen name="sharing_hub_3p_icon_size">36dp</dimen>
+    <dimen name="sharing_hub_3p_icon_padding_top">20dp</dimen>
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
index ed15b5d..3d568912 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -257,17 +257,6 @@
     }
 
     /**
-     * TODO(crbug.com/1102812) : Remove this method after updating the downstream to use the new
-     * method {@link getOfflinePagesCctAllowlist} instead.
-     * @return A list of allowlisted apps that are allowed to receive notification when the
-     * set of offlined pages downloaded on their behalf has changed. Apps are listed by their
-     * package name.
-     */
-    public List<String> getOfflinePagesCctWhitelist() {
-        return Collections.emptyList();
-    }
-
-    /**
      * @return A list of allowlisted apps that are allowed to receive notification when the
      * set of offlined pages downloaded on their behalf has changed. Apps are listed by their
      * package name.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
index b2e67c3f..45d7cb4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
@@ -15,6 +15,7 @@
 import androidx.appcompat.app.AppCompatActivity;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.base.SplitCompatUtils;
 import org.chromium.chrome.browser.language.GlobalAppLocaleController;
 import org.chromium.chrome.browser.night_mode.GlobalNightModeStateProviderHolder;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
@@ -46,6 +47,8 @@
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
+        getSupportFragmentManager().setFragmentFactory(SplitCompatUtils.createFragmentFactory());
+
         initializeNightModeStateProvider();
         mNightModeStateProvider.addObserver(this);
         super.onCreate(savedInstanceState);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatUtils.java
index e8efe77..87a1339 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatUtils.java
@@ -8,12 +8,18 @@
 import android.content.ContextWrapper;
 import android.view.LayoutInflater;
 
+import androidx.collection.ArraySet;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentFactory;
+
 import org.chromium.base.BundleUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.IdentifierNameString;
 
 /** Utils for compatibility with isolated splits. */
 public class SplitCompatUtils {
+    private static final ArraySet<ClassLoader> sInflationClassLoaders = new ArraySet<>();
+
     private SplitCompatUtils() {}
 
     /**
@@ -57,6 +63,9 @@
         }
         ClassLoader splitClassLoader =
                 BundleUtils.createIsolatedSplitContext(context, splitName).getClassLoader();
+        // All Contexts for a split share a ClassLoader, so the maximum size of this set will be the
+        // number of installed splits.
+        sInflationClassLoaders.add(splitClassLoader);
         return new ContextWrapper(context) {
             @Override
             public ClassLoader getClassLoader() {
@@ -73,4 +82,36 @@
             }
         };
     }
+
+    /**
+     * Returns a FragmentFactory which can load fragment classes from any split which an inflation
+     * context has been created for. This is useful if a fragment lives in an isolated split and is
+     * not retained. It may be recreated on configuration changes, and will need to be loaded from
+     * the correct ClassLoader.
+     */
+    public static FragmentFactory createFragmentFactory() {
+        return new FragmentFactory() {
+            @Override
+            public Fragment instantiate(ClassLoader classLoader, String className) {
+                if (!canLoadClass(classLoader, className)) {
+                    for (ClassLoader cl : sInflationClassLoaders) {
+                        if (canLoadClass(cl, className)) {
+                            classLoader = cl;
+                            break;
+                        }
+                    }
+                }
+                return super.instantiate(classLoader, className);
+            }
+        };
+    }
+
+    private static boolean canLoadClass(ClassLoader classLoader, String className) {
+        try {
+            Class.forName(className, false, classLoader);
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
 }
diff --git a/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps b/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
index a75412e1..0d663af 100644
--- a/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
+++ b/chrome/android/monochrome/scripts/monochrome_python_tests.pydeps
@@ -4,6 +4,7 @@
 //build/android/pylib/__init__.py
 //build/android/pylib/constants/__init__.py
 //build/android/pylib/constants/host_paths.py
+//build/gn_helpers.py
 //chrome/android/monochrome/scripts/monochrome_android_manifest_test.py
 //chrome/android/monochrome/scripts/monochrome_apk_checker_test.py
 //chrome/android/monochrome/scripts/monochrome_dexdump_test.py
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e303911..c7b3e2a 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1101,6 +1101,8 @@
     "performance_manager/chrome_browser_main_extra_parts_performance_manager.h",
     "performance_manager/chrome_content_browser_client_performance_manager_part.cc",
     "performance_manager/chrome_content_browser_client_performance_manager_part.h",
+    "performance_manager/decorators/execution_context_priority/frame_visibility_voter.cc",
+    "performance_manager/decorators/execution_context_priority/frame_visibility_voter.h",
     "performance_manager/decorators/execution_context_priority/root_vote_observer.cc",
     "performance_manager/decorators/execution_context_priority/root_vote_observer.h",
     "performance_manager/decorators/frozen_frame_aggregator.cc",
@@ -1771,10 +1773,10 @@
     "subresource_filter/subresource_filter_history_observer.h",
     "subresource_filter/subresource_filter_profile_context_factory.cc",
     "subresource_filter/subresource_filter_profile_context_factory.h",
-    "subresource_redirect/https_image_compression_bypass_decider.cc",
-    "subresource_redirect/https_image_compression_bypass_decider.h",
     "subresource_redirect/https_image_compression_infobar_decider.cc",
     "subresource_redirect/https_image_compression_infobar_decider.h",
+    "subresource_redirect/litepages_service_bypass_decider.cc",
+    "subresource_redirect/litepages_service_bypass_decider.h",
     "subresource_redirect/origin_robots_rules.cc",
     "subresource_redirect/origin_robots_rules.h",
     "subresource_redirect/subresource_redirect_observer.cc",
@@ -1972,7 +1974,6 @@
     "//chrome/browser/resource_coordinator:tab_manager_features",
     "//chrome/browser/safe_browsing",
     "//chrome/browser/safe_browsing:advanced_protection",
-    "//chrome/browser/safe_browsing:url_lookup_service_factory",
     "//chrome/browser/search/task_module:mojo_bindings",
     "//chrome/browser/sharing:buildflags",
     "//chrome/browser/sharing/proto",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 60fcd5f..7f9b782 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3112,10 +3112,6 @@
      flag_descriptions::kLockScreenNotificationDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kLockScreenNotifications)},
 #endif  // OS_CHROMEOS
-    {"reduced-referrer-granularity",
-     flag_descriptions::kReducedReferrerGranularityName,
-     flag_descriptions::kReducedReferrerGranularityDescription, kOsAll,
-     FEATURE_VALUE_TYPE(blink::features::kReducedReferrerGranularity)},
 #if defined(OS_CHROMEOS)
     {"crostini-use-dlc", flag_descriptions::kCrostiniUseDlcName,
      flag_descriptions::kCrostiniUseDlcDescription, kOsCrOS,
diff --git a/chrome/browser/android/content/content_utils.cc b/chrome/browser/android/content/content_utils.cc
index 388b88c..3535fbf 100644
--- a/chrome/browser/android/content/content_utils.cc
+++ b/chrome/browser/android/content/content_utils.cc
@@ -5,9 +5,8 @@
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/ContentUtils_jni.h"
 #include "chrome/browser/chrome_content_browser_client.h"
+#include "components/embedder_support/android/util/user_agent_utils.h"
 #include "components/version_info/version_info.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/common/user_agent.h"
 
 static base::android::ScopedJavaLocalRef<jstring>
 JNI_ContentUtils_GetBrowserUserAgent(JNIEnv* env) {
@@ -17,21 +16,8 @@
 static void JNI_ContentUtils_SetUserAgentOverride(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& jweb_contents) {
-  const char kLinuxInfoStr[] = "X11; Linux x86_64";
-  std::string product = version_info::GetProductNameAndVersionForUserAgent();
-
-  blink::UserAgentOverride spoofed_ua;
-  spoofed_ua.ua_string_override =
-      content::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
-  spoofed_ua.ua_metadata_override = ::GetUserAgentMetadata();
-  spoofed_ua.ua_metadata_override->platform = "Linux";
-  spoofed_ua.ua_metadata_override->platform_version =
-      std::string();  // match content::GetOSVersion(false) on Linux
-  spoofed_ua.ua_metadata_override->architecture = "x86";
-  spoofed_ua.ua_metadata_override->model = std::string();
-  spoofed_ua.ua_metadata_override->mobile = false;
-
   content::WebContents* web_contents =
       content::WebContents::FromJavaWebContents(jweb_contents);
-  web_contents->SetUserAgentOverride(spoofed_ua, false);
+  embedder_support::SetDesktopUserAgentOverride(web_contents,
+                                                ::GetUserAgentMetadata());
 }
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index db76325..613e0b85 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -152,6 +152,43 @@
       chromeos::switches::kDisableVolumeAdjustSound);
 }
 
+std::string AccessibilityPrivateEnumForAction(
+    ash::SelectToSpeakPanelAction action) {
+  switch (action) {
+    case ash::SelectToSpeakPanelAction::kPreviousSentence:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_PREVIOUSSENTENCE);
+    case ash::SelectToSpeakPanelAction::kPreviousParagraph:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_PREVIOUSPARAGRAPH);
+    case ash::SelectToSpeakPanelAction::kPause:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_PAUSE);
+    case ash::SelectToSpeakPanelAction::kResume:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_RESUME);
+    case ash::SelectToSpeakPanelAction::kNextSentence:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_NEXTSENTENCE);
+    case ash::SelectToSpeakPanelAction::kNextParagraph:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_NEXTPARAGRAPH);
+    case ash::SelectToSpeakPanelAction::kExit:
+      return extensions::api::accessibility_private::ToString(
+          extensions::api::accessibility_private::
+              SELECT_TO_SPEAK_PANEL_ACTION_EXIT);
+    case ash::SelectToSpeakPanelAction::kNone:
+      NOTREACHED();
+      return "";
+  }
+}
+
 }  // namespace
 
 class AccessibilityPanelWidgetObserver : public views::WidgetObserver {
@@ -1659,4 +1696,25 @@
   profile_->GetPrefs()->CommitPendingWrite();
 }
 
+// Sends a panel action event to the Select-to-speak extension.
+void AccessibilityManager::OnSelectToSpeakPanelAction(
+    ash::SelectToSpeakPanelAction action) {
+  if (!profile_)
+    return;
+
+  extensions::EventRouter* event_router =
+      extensions::EventRouter::Get(profile_);
+
+  auto event_args = std::make_unique<base::ListValue>();
+  event_args->AppendString(AccessibilityPrivateEnumForAction(action));
+
+  auto event = std::make_unique<extensions::Event>(
+      extensions::events::ACCESSIBILITY_PRIVATE_ON_SELECT_TO_SPEAK_PANEL_ACTION,
+      extensions::api::accessibility_private::OnSelectToSpeakPanelAction::
+          kEventName,
+      std::move(event_args));
+  event_router->DispatchEventWithLazyListener(
+      extension_misc::kSelectToSpeakExtensionId, std::move(event));
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index 4ed94bd..30bfdf9 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -40,6 +40,7 @@
 namespace ash {
 struct AccessibilityFocusRingInfo;
 enum class SelectToSpeakState;
+enum class SelectToSpeakPanelAction;
 }  // namespace ash
 
 namespace gfx {
@@ -337,6 +338,9 @@
   const std::string GetFocusRingId(const std::string& extension_id,
                                    const std::string& focus_ring_name);
 
+  // Sends a panel action event to the Select-to-speak extension.
+  void OnSelectToSpeakPanelAction(ash::SelectToSpeakPanelAction action);
+
   // Test helpers:
   void SetProfileForTest(Profile* profile);
   static void SetBrailleControllerForTest(
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
index 57b73e8..dfabd7a 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_browsertest.cc
@@ -619,6 +619,22 @@
   EXPECT_FALSE(is_key_on_token_waiter.on_slot().value());
 }
 
+IN_PROC_BROWSER_TEST_P(PlatformKeysServicePerTokenBrowserTest,
+                       GetKeyLocations) {
+  const TokenId token_id = GetParam().token_id;
+
+  const std::string public_key = GenerateKeyPair(token_id);
+
+  test_util::GetKeyLocationsExecutionWaiter get_key_locations_waiter;
+  platform_keys_service()->GetKeyLocations(
+      public_key, get_key_locations_waiter.GetCallback());
+  get_key_locations_waiter.Wait();
+
+  EXPECT_EQ(get_key_locations_waiter.status(), Status::kSuccess);
+  ASSERT_EQ(get_key_locations_waiter.key_locations().size(), 1U);
+  EXPECT_EQ(get_key_locations_waiter.key_locations()[0], token_id);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     AllSupportedProfilesAndTokensTypes,
     PlatformKeysServicePerTokenBrowserTest,
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
index 7fcf023..2d26e8d 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_nss.cc
@@ -1309,7 +1309,11 @@
     if (rsa_key)
       token_ids.push_back(TokenId::kUser);
   }
-  if (token_ids.empty() && cert_db->GetPublicSlot().get()) {
+
+  // The "system" NSSCertDatabaseChromeOS instance reuses its "system slot" as
+  // "public slot", but that doesn't mean it's a user-specific slot.
+  if (token_ids.empty() && cert_db->GetPublicSlot().get() &&
+      cert_db->GetPublicSlot().get() != cert_db->GetSystemSlot().get()) {
     crypto::ScopedSECKEYPrivateKey rsa_key =
         crypto::FindNSSKeyFromPublicKeyInfoInSlot(
             public_key_vector, cert_db->GetPublicSlot().get());
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.cc
index e0a804a..c13c598 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.cc
@@ -41,6 +41,9 @@
 IsKeyOnTokenExecutionWaiter::IsKeyOnTokenExecutionWaiter() = default;
 IsKeyOnTokenExecutionWaiter::~IsKeyOnTokenExecutionWaiter() = default;
 
+GetKeyLocationsExecutionWaiter::GetKeyLocationsExecutionWaiter() = default;
+GetKeyLocationsExecutionWaiter::~GetKeyLocationsExecutionWaiter() = default;
+
 }  // namespace test_util
 }  // namespace platform_keys
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.h b/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.h
index fc99cd6..ad5fef48 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.h
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service_test_util.h
@@ -183,6 +183,17 @@
   }
 };
 
+class GetKeyLocationsExecutionWaiter
+    : public ExecutionWaiter<const std::vector<TokenId>&> {
+ public:
+  GetKeyLocationsExecutionWaiter();
+  ~GetKeyLocationsExecutionWaiter();
+
+  const std::vector<TokenId>& key_locations() const {
+    return std::get<0>(result_callback_args());
+  }
+};
+
 }  // namespace test_util
 }  // namespace platform_keys
 }  // namespace chromeos
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
index fbea49934..78def63 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.cc
@@ -26,8 +26,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/renderer_host/chrome_navigation_ui_data.h"
-#include "chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h"
 #include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
+#include "chrome/browser/subresource_redirect/litepages_service_bypass_decider.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/pref_names.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h"
@@ -249,8 +249,8 @@
     https_image_compression_infobar_decider_ =
         std::make_unique<HttpsImageCompressionInfoBarDecider>(profile_prefs,
                                                               this);
-    https_image_compression_bypass_decider_ =
-        std::make_unique<HttpsImageCompressionBypassDecider>();
+    litepages_service_bypass_decider_ =
+        std::make_unique<LitePagesServiceBypassDecider>();
   }
 }
 
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
index d4a10222..36dd83d00d 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h
@@ -29,8 +29,8 @@
 class DataStore;
 }  // namespace data_reduction_proxy
 
-class HttpsImageCompressionBypassDecider;
 class HttpsImageCompressionInfoBarDecider;
+class LitePagesServiceBypassDecider;
 class PrefService;
 
 // Data reduction proxy settings class suitable for use with a Chrome browser.
@@ -87,9 +87,8 @@
     return https_image_compression_infobar_decider_.get();
   }
 
-  HttpsImageCompressionBypassDecider* https_image_compression_bypass_decider()
-      const {
-    return https_image_compression_bypass_decider_.get();
+  LitePagesServiceBypassDecider* litepages_service_bypass_decider() const {
+    return litepages_service_bypass_decider_.get();
   }
 
  private:
@@ -104,10 +103,10 @@
   std::unique_ptr<HttpsImageCompressionInfoBarDecider>
       https_image_compression_infobar_decider_;
 
-  // Maintains the decider for this profile to contain logic for https image
-  // compression bypass.
-  std::unique_ptr<HttpsImageCompressionBypassDecider>
-      https_image_compression_bypass_decider_;
+  // Maintains the decider for this profile to contain logic for LitePages
+  // service bypass.
+  std::unique_ptr<LitePagesServiceBypassDecider>
+      litepages_service_bypass_decider_;
 
   // Null before InitDataReductionProxySettings is called.
   Profile* profile_;
diff --git a/chrome/browser/data_saver/subresource_redirect_browsertest.cc b/chrome/browser/data_saver/subresource_redirect_browsertest.cc
index f2f4b37..8259eb5 100644
--- a/chrome/browser/data_saver/subresource_redirect_browsertest.cc
+++ b/chrome/browser/data_saver/subresource_redirect_browsertest.cc
@@ -13,8 +13,8 @@
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h"
 #include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
+#include "chrome/browser/subresource_redirect/litepages_service_bypass_decider.h"
 #include "chrome/browser/subresource_redirect/subresource_redirect_observer.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_paths.h"
@@ -219,9 +219,8 @@
   void VerifyPublicImageCompressionUkm(uint64_t hash, size_t num_images) {
     const auto metrics = GetImageCompressionUkmMetrics();
     if (num_images) {
-      EXPECT_THAT(metrics, testing::Contains(
-                               testing::Pair(hash,
-                                             testing::Gt(num_images * 500))));
+      EXPECT_THAT(metrics, testing::Contains(testing::Pair(
+                               hash, testing::Gt(num_images * 500))));
     } else {
       EXPECT_EQ(metrics.find(hash), metrics.end());
     }
@@ -296,18 +295,17 @@
   }
 
   void VerifyAndClearBypassTimeout(base::TimeDelta minimum_bypass_until) {
-    auto* https_image_compression_bypass_decider =
+    auto* litepages_service_bypass_decider =
         DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
             browser()
                 ->tab_strip_model()
                 ->GetActiveWebContents()
                 ->GetBrowserContext())
-            ->https_image_compression_bypass_decider();
-    EXPECT_LE(
-        base::TimeTicks::Now() + minimum_bypass_until,
-        https_image_compression_bypass_decider->GetBypassUntilTimeForTesting()
-            .value());
-    https_image_compression_bypass_decider->SetBypassUntilTimeForTesting(
+            ->litepages_service_bypass_decider();
+    EXPECT_LE(base::TimeTicks::Now() + minimum_bypass_until,
+              litepages_service_bypass_decider->GetBypassUntilTimeForTesting()
+                  .value());
+    litepages_service_bypass_decider->SetBypassUntilTimeForTesting(
         base::TimeTicks::Now());
   }
 
@@ -527,7 +525,7 @@
   histogram_tester()->ExpectUniqueSample(
       "SubresourceRedirect.CompressionFetchTimeout", true, 1);
   histogram_tester()->ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
   histogram_tester()->ExpectTotalCount("SubresourceRedirect.BypassDuration", 1);
 
   // The second navigation should not attempt subresource redirect.
@@ -538,9 +536,10 @@
 
   base::RunLoop().RunUntilIdle();
   RetryForHistogramUntilCountReached(
-      histogram_tester(), "SubresourceRedirect.PageLoad.BypassResult", 2);
+      histogram_tester(), "SubresourceRedirect.LitePagesService.BypassResult",
+      2);
   histogram_tester()->ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 1);
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 1);
 
   // The third navigation should attempt subresource redirect, once the bypass
   // is cleared.
@@ -1268,7 +1267,7 @@
   RetryForHistogramUntilCountReached(histogram_tester(),
                                      "SubresourceRedirect.BypassDuration", 1);
   histogram_tester()->ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
   histogram_tester()->ExpectTotalCount("SubresourceRedirect.BypassDuration", 1);
 
   // The second navigation should not attempt subresource redirect.
@@ -1279,9 +1278,10 @@
 
   base::RunLoop().RunUntilIdle();
   RetryForHistogramUntilCountReached(
-      histogram_tester(), "SubresourceRedirect.PageLoad.BypassResult", 2);
+      histogram_tester(), "SubresourceRedirect.LitePagesService.BypassResult",
+      2);
   histogram_tester()->ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 1);
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 1);
 
   // The third navigation should attempt subresource redirect, once the bypass
   // is cleared.
diff --git a/chrome/browser/extensions/api/scripting/scripting_api.cc b/chrome/browser/extensions/api/scripting/scripting_api.cc
index 14b0c982c..834a490 100644
--- a/chrome/browser/extensions/api/scripting/scripting_api.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_api.cc
@@ -4,12 +4,15 @@
 
 #include "chrome/browser/extensions/api/scripting/scripting_api.h"
 
+#include <utility>
+
 #include "base/check.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/tab_helper.h"
 #include "chrome/common/extensions/api/scripting.h"
 #include "extensions/browser/extension_api_frame_id_map.h"
+#include "extensions/browser/load_and_localize_file.h"
 #include "extensions/browser/script_executor.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/extension.h"
@@ -21,6 +24,8 @@
 
 namespace {
 
+constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
+
 // Returns true if the `permissions` allow for injection into the given `frame`.
 // If false, populates `error`.
 bool HasPermissionToInjectIntoFrame(const PermissionsData& permissions,
@@ -121,6 +126,54 @@
   return true;
 }
 
+// Returns true if the loaded resource is valid for injection.
+bool CheckLoadedResource(bool success,
+                         std::string* data,
+                         const std::string& file_name,
+                         std::string* error) {
+  if (!success) {
+    *error = ErrorUtils::FormatErrorMessage(kCouldNotLoadFileError, file_name);
+    return false;
+  }
+
+  DCHECK(data);
+  // TODO(devlin): What necessitates this encoding requirement? Is it needed for
+  // blink injection?
+  if (!base::IsStringUTF8(*data)) {
+    constexpr char kBadFileEncodingError[] =
+        "Could not load file '*'. It isn't UTF-8 encoded.";
+    *error = ErrorUtils::FormatErrorMessage(kBadFileEncodingError, file_name);
+    return false;
+  }
+
+  return true;
+}
+
+// Checks the specified `files` for validity, and attempts to load and localize
+// them, invoking `callback` with the result. Returns true on success; on
+// failure, populates `error`.
+bool CheckAndLoadFiles(const std::vector<std::string>& files,
+                       const Extension& extension,
+                       bool requires_localization,
+                       LoadAndLocalizeResourceCallback callback,
+                       std::string* error) {
+  if (files.size() != 1) {
+    constexpr char kExactlyOneFileError[] =
+        "Exactly one file must be specified.";
+    *error = kExactlyOneFileError;
+    return false;
+  }
+  ExtensionResource resource = extension.GetResource(files[0]);
+  if (resource.extension_root().empty() || resource.relative_path().empty()) {
+    *error = ErrorUtils::FormatErrorMessage(kCouldNotLoadFileError, files[0]);
+    return false;
+  }
+
+  LoadAndLocalizeResource(extension, resource, requires_localization,
+                          std::move(callback));
+  return true;
+}
+
 }  // namespace
 
 ScriptingExecuteScriptFunction::ScriptingExecuteScriptFunction() = default;
@@ -130,40 +183,83 @@
   std::unique_ptr<api::scripting::ExecuteScript::Params> params(
       api::scripting::ExecuteScript::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  const api::scripting::ScriptInjection& injection = params->injection;
+  injection_ = std::move(params->injection);
 
-  // TODO(devlin): Add support for specifying a file.
-  if (!injection.function)
-    return RespondNow(Error("'css' must be specified"));
-
-  std::string error;
-  ScriptExecutor* script_executor = nullptr;
-  ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
-  std::vector<int> frame_ids;
-  if (!CanAccessTarget(*extension()->permissions_data(), injection.target,
-                       browser_context(), include_incognito_information(),
-                       &script_executor, &frame_scope, &frame_ids, &error)) {
-    return RespondNow(Error(std::move(error)));
+  if ((injection_.files && injection_.function) ||
+      (!injection_.files && !injection_.function)) {
+    return RespondNow(
+        Error("Exactly one of 'function' and 'files' must be specified"));
   }
 
-  DCHECK(script_executor);
+  if (injection_.files) {
+    // JS files don't require localization.
+    constexpr bool kRequiresLocalization = false;
+    std::string error;
+    if (!CheckAndLoadFiles(
+            *injection_.files, *extension(), kRequiresLocalization,
+            base::BindOnce(&ScriptingExecuteScriptFunction::DidLoadResource,
+                           this),
+            &error)) {
+      return RespondNow(Error(std::move(error)));
+    }
+    return RespondLater();
+  }
+
+  DCHECK(injection_.function);
 
   // TODO(devlin): This (wrapping a function to create an IIFE) is pretty hacky,
   // and won't work well when we support currying arguments. Add support to the
   // ScriptExecutor to better support this case.
   std::string code_to_execute =
-      base::StringPrintf("(%s)()", injection.function->c_str());
+      base::StringPrintf("(%s)()", injection_.function->c_str());
+
+  std::string error;
+  if (!Execute(std::move(code_to_execute), /*script_src=*/GURL(), &error))
+    return RespondNow(Error(std::move(error)));
+
+  return RespondLater();
+}
+
+void ScriptingExecuteScriptFunction::DidLoadResource(
+    bool success,
+    std::unique_ptr<std::string> data) {
+  DCHECK(injection_.files);
+  DCHECK_EQ(1u, injection_.files->size());
+
+  std::string error;
+  if (!CheckLoadedResource(success, data.get(), injection_.files->at(0),
+                           &error)) {
+    Respond(Error(std::move(error)));
+    return;
+  }
+
+  GURL script_url = extension()->GetResourceURL(injection_.files->at(0));
+  if (!Execute(std::move(*data), std::move(script_url), &error))
+    Respond(Error(std::move(error)));
+}
+
+bool ScriptingExecuteScriptFunction::Execute(std::string code_to_execute,
+                                             GURL script_url,
+                                             std::string* error) {
+  ScriptExecutor* script_executor = nullptr;
+  ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
+  std::vector<int> frame_ids;
+  if (!CanAccessTarget(*extension()->permissions_data(), injection_.target,
+                       browser_context(), include_incognito_information(),
+                       &script_executor, &frame_scope, &frame_ids, error)) {
+    return false;
+  }
 
   script_executor->ExecuteScript(
       HostID(HostID::EXTENSIONS, extension()->id()), UserScript::ADD_JAVASCRIPT,
-      code_to_execute, frame_scope, frame_ids,
+      std::move(code_to_execute), frame_scope, frame_ids,
       ScriptExecutor::MATCH_ABOUT_BLANK, UserScript::DOCUMENT_IDLE,
       ScriptExecutor::DEFAULT_PROCESS,
-      /* webview_src */ GURL(), /* script_url */ GURL(), user_gesture(),
+      /* webview_src */ GURL(), std::move(script_url), user_gesture(),
       base::nullopt, ScriptExecutor::JSON_SERIALIZED_RESULT,
       base::BindOnce(&ScriptingExecuteScriptFunction::OnScriptExecuted, this));
 
-  return RespondLater();
+  return true;
 }
 
 void ScriptingExecuteScriptFunction::OnScriptExecuted(
@@ -196,20 +292,66 @@
   std::unique_ptr<api::scripting::InsertCSS::Params> params(
       api::scripting::InsertCSS::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
-  api::scripting::CSSInjection injection = std::move(params->injection);
 
-  // TODO(https://crbug.com/1148880): Add support for specifying a file.
-  if (!injection.css)
-    return RespondNow(Error("'css' must be specified"));
+  injection_ = std::move(params->injection);
+
+  if ((injection_.files && injection_.css) ||
+      (!injection_.files && !injection_.css)) {
+    return RespondNow(
+        Error("Exactly one of 'css' and 'files' must be specified"));
+  }
+
+  if (injection_.files) {
+    // CSS files require localization.
+    constexpr bool kRequiresLocalization = true;
+    std::string error;
+    if (!CheckAndLoadFiles(
+            *injection_.files, *extension(), kRequiresLocalization,
+            base::BindOnce(&ScriptingInsertCSSFunction::DidLoadResource, this),
+            &error)) {
+      return RespondNow(Error(std::move(error)));
+    }
+    return RespondLater();
+  }
+
+  DCHECK(injection_.css);
 
   std::string error;
+  if (!Execute(std::move(*injection_.css), /*script_url=*/GURL(), &error)) {
+    return RespondNow(Error(std::move(error)));
+  }
+
+  return RespondLater();
+}
+
+void ScriptingInsertCSSFunction::DidLoadResource(
+    bool success,
+    std::unique_ptr<std::string> data) {
+  DCHECK(injection_.files);
+  DCHECK_EQ(1u, injection_.files->size());
+
+  std::string error;
+  if (!CheckLoadedResource(success, data.get(), injection_.files->at(0),
+                           &error)) {
+    Respond(Error(std::move(error)));
+    return;
+  }
+
+  GURL script_url = extension()->GetResourceURL(injection_.files->at(0));
+  if (!Execute(std::move(*data), std::move(script_url), &error))
+    Respond(Error(std::move(error)));
+}
+
+bool ScriptingInsertCSSFunction::Execute(std::string code_to_execute,
+                                         GURL script_url,
+                                         std::string* error) {
   ScriptExecutor* script_executor = nullptr;
   ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
   std::vector<int> frame_ids;
-  if (!CanAccessTarget(*extension()->permissions_data(), injection.target,
+  if (!CanAccessTarget(*extension()->permissions_data(), injection_.target,
                        browser_context(), include_incognito_information(),
-                       &script_executor, &frame_scope, &frame_ids, &error)) {
-    return RespondNow(Error(std::move(error)));
+                       &script_executor, &frame_scope, &frame_ids, error)) {
+    return false;
   }
   DCHECK(script_executor);
 
@@ -217,7 +359,7 @@
   // pass it through ScriptExecutor and friends, rather than having it
   // defined in the renderer.
   base::Optional<CSSOrigin> origin;
-  switch (injection.origin) {
+  switch (injection_.origin) {
     case api::scripting::STYLE_ORIGIN_USER:
       origin = CSS_ORIGIN_USER;
       break;
@@ -234,13 +376,14 @@
   constexpr UserScript::RunLocation kRunLocation = UserScript::DOCUMENT_START;
   script_executor->ExecuteScript(
       HostID(HostID::EXTENSIONS, extension()->id()), UserScript::ADD_CSS,
-      *injection.css, frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK,
-      kRunLocation, ScriptExecutor::DEFAULT_PROCESS,
-      /* webview_src */ GURL(), /* script_url */ GURL(), user_gesture(), origin,
+      std::move(code_to_execute), frame_scope, frame_ids,
+      ScriptExecutor::MATCH_ABOUT_BLANK, kRunLocation,
+      ScriptExecutor::DEFAULT_PROCESS,
+      /* webview_src */ GURL(), std::move(script_url), user_gesture(), origin,
       ScriptExecutor::NO_RESULT,
       base::BindOnce(&ScriptingInsertCSSFunction::OnCSSInserted, this));
 
-  return RespondLater();
+  return true;
 }
 
 void ScriptingInsertCSSFunction::OnCSSInserted(const std::string& error,
diff --git a/chrome/browser/extensions/api/scripting/scripting_api.h b/chrome/browser/extensions/api/scripting/scripting_api.h
index c66588c..7fa7dcea 100644
--- a/chrome/browser/extensions/api/scripting/scripting_api.h
+++ b/chrome/browser/extensions/api/scripting/scripting_api.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "chrome/common/extensions/api/scripting.h"
 #include "extensions/browser/extension_function.h"
 
 class GURL;
@@ -33,10 +34,21 @@
  private:
   ~ScriptingExecuteScriptFunction() override;
 
+  // Called when the resource file to be injected has been loaded.
+  void DidLoadResource(bool success, std::unique_ptr<std::string> data);
+
+  // Triggers the execution of `code_to_execute` in the appropriate context.
+  // Returns true on success; on failure, populates `error`.
+  bool Execute(std::string code_to_execute,
+               GURL script_url,
+               std::string* error);
+
   // Invoked when script execution is complete.
   void OnScriptExecuted(const std::string& error,
                         const GURL& frame_url,
                         const base::ListValue& result);
+
+  api::scripting::ScriptInjection injection_;
 };
 
 class ScriptingInsertCSSFunction : public ExtensionFunction {
@@ -54,10 +66,21 @@
  private:
   ~ScriptingInsertCSSFunction() override;
 
+  // Called when the resource file to be injected has been loaded.
+  void DidLoadResource(bool success, std::unique_ptr<std::string> data);
+
+  // Triggers the execution of `code_to_execute` in the appropriate context.
+  // Returns true on success; on failure, populates `error`.
+  bool Execute(std::string code_to_execute,
+               GURL script_url,
+               std::string* error);
+
   // Called when the CSS insertion is complete.
   void OnCSSInserted(const std::string& error,
                      const GURL& frame_url,
                      const base::ListValue& result);
+
+  api::scripting::CSSInjection injection_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 887c774..b75fb8e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -514,7 +514,7 @@
   {
     "name": "chrome-sharing-hub-v1-5",
     "owners": [ "sophey" ],
-    "expiry_milestone": 89
+    "expiry_milestone": 90
   },
   {
     "name": "chromeos-direct-video-decoder",
@@ -4239,7 +4239,7 @@
   {
     "name": "share-button-in-top-toolbar",
     "owners": [ "jeffreycohen" ],
-    "expiry_milestone": 88
+    "expiry_milestone": 90
   },
   {
     "name": "share-by-default-in-cct",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b99df2a..c6d28c4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1927,13 +1927,6 @@
     "Allow users to save tabs for later. Enables a new button and menu for "
     "accessing tabs saved for later.";
 
-const char kReducedReferrerGranularityName[] =
-    "Reduce default 'referer' header granularity.";
-const char kReducedReferrerGranularityDescription[] =
-    "If a page hasn't set an explicit referrer policy, setting this flag will "
-    "reduce the amount of information in the 'referer' header for cross-origin "
-    "requests.";
-
 const char kRewriteLevelDBOnDeletionName[] =
     "Rewrite LevelDB instances after full deletions";
 const char kRewriteLevelDBOnDeletionDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index dbde51e..8fadd35 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1124,9 +1124,6 @@
 extern const char kReadLaterName[];
 extern const char kReadLaterDescription[];
 
-extern const char kReducedReferrerGranularityName[];
-extern const char kReducedReferrerGranularityDescription[];
-
 extern const char kRewriteLevelDBOnDeletionName[];
 extern const char kRewriteLevelDBOnDeletionDescription[];
 
diff --git a/chrome/browser/font_access/OWNERS b/chrome/browser/font_access/OWNERS
new file mode 100644
index 0000000..da6ba84
--- /dev/null
+++ b/chrome/browser/font_access/OWNERS
@@ -0,0 +1,5 @@
+jsbell@chromium.com
+oyiptong@chromium.org
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>FontAccess
diff --git a/chrome/browser/font_access/font_access_context_browsertest.cc b/chrome/browser/font_access/font_access_context_browsertest.cc
new file mode 100644
index 0000000..e69c357
--- /dev/null
+++ b/chrome/browser/font_access/font_access_context_browsertest.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/font_access_context.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
+
+namespace {
+
+class FontAccessContextBrowserTest : public InProcessBrowserTest {
+ public:
+  FontAccessContextBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(blink::features::kFontAccess);
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+#if defined(PLATFORM_HAS_LOCAL_FONT_ENUMERATION_IMPL)
+
+IN_PROC_BROWSER_TEST_F(FontAccessContextBrowserTest, BasicEnumerationTest) {
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), ui_test_utils::GetTestUrl(
+                     base::FilePath(),
+                     base::FilePath(FILE_PATH_LITERAL("simple_page.html")))));
+  content::FontAccessContext* context = browser()
+                                            ->tab_strip_model()
+                                            ->GetActiveWebContents()
+                                            ->GetMainFrame()
+                                            ->GetStoragePartition()
+                                            ->GetFontAccessContext();
+
+  base::RunLoop run_loop;
+  context->FindAllFonts(base::BindLambdaForTesting(
+      [&](blink::mojom::FontEnumerationStatus status,
+          std::vector<blink::mojom::FontMetadata> fonts) {
+        EXPECT_EQ(status, blink::mojom::FontEnumerationStatus::kOk)
+            << "Enumeration expected to be successful.";
+        EXPECT_GT(fonts.size(), 0u)
+            << "Enumeration expected to yield at least 1 font";
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+#endif
+
+}  // namespace
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service.cc b/chrome/browser/long_screenshots/long_screenshots_tab_service.cc
index 30e7468..26b84f57 100644
--- a/chrome/browser/long_screenshots/long_screenshots_tab_service.cc
+++ b/chrome/browser/long_screenshots/long_screenshots_tab_service.cc
@@ -11,6 +11,9 @@
 
 namespace long_screenshots {
 
+using paint_preview::DirectoryKey;
+using paint_preview::FileManager;
+
 namespace {
 // TODO(tgupta): Evaluate whether this is the right size.
 constexpr size_t kMaxPerCaptureSizeBytes = 5 * 1000L * 1000L;  // 5 MB.
@@ -104,20 +107,37 @@
     FinishedCallback callback,
     paint_preview::PaintPreviewBaseService::CaptureStatus status,
     std::unique_ptr<paint_preview::CaptureResult> result) {
-  // TODO(tgupta): Continue to flesh this out to manage the documents. Consider
-  // having more detailed status messages to return to the caller.
+  auto* web_contents =
+      content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
+  if (web_contents)
+    web_contents->DecrementCapturerCount(true);
+
   if (status != PaintPreviewBaseService::CaptureStatus::kOk ||
       !result->capture_success) {
     std::move(callback).Run(Status::kCaptureFailed);
     return;
   }
-  std::move(callback).Run(Status::kOk);
+
+  auto file_manager = GetFileManager();
+  GetTaskRunner()->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&FileManager::SerializePaintPreviewProto, GetFileManager(),
+                     key, result->proto, true),
+      base::BindOnce(&LongScreenshotsTabService::OnFinished,
+                     weak_ptr_factory_.GetWeakPtr(), tab_id,
+                     std::move(callback)));
 }
 
 void LongScreenshotsTabService::OnFinished(int tab_id,
                                            FinishedCallback callback,
                                            bool success) {
-  // TODO(tgupta): Complete this function
+  std::move(callback).Run(success ? Status::kOk
+                                  : Status::kProtoSerializationFailed);
+}
+
+void LongScreenshotsTabService::DeleteAllLongScreenshotFiles() {
+  GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&FileManager::DeleteAll, GetFileManager()));
 }
 
 }  // namespace long_screenshots
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service.h b/chrome/browser/long_screenshots/long_screenshots_tab_service.h
index 375e2af..66c88e6 100644
--- a/chrome/browser/long_screenshots/long_screenshots_tab_service.h
+++ b/chrome/browser/long_screenshots/long_screenshots_tab_service.h
@@ -26,6 +26,9 @@
 
 // A service for capturing Long Screenshots using PaintPreview. Writes the
 // retrieved bitmap to file.
+// TODO(tgupta): Handle the deletion of old files when the long screenshots
+// feature ends or when Chrome starts up (to handle when Chrome is killed in the
+// background and there was no opportunity to clean the files).
 class LongScreenshotsTabService
     : public paint_preview::PaintPreviewBaseService {
  public:
@@ -53,6 +56,9 @@
                   content::WebContents* contents,
                   FinishedCallback callback);
 
+  // Delete all old long screenshot files.
+  void DeleteAllLongScreenshotFiles();
+
  private:
   // Retrieves the content::WebContents from the |frame_tree_node_id|
   // (confirming that the contents are alive using the |frame_routing_id|).
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
index d73bab9..1f644de 100644
--- a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
+++ b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
@@ -22,6 +22,9 @@
 
 namespace long_screenshots {
 
+using paint_preview::DirectoryKey;
+using paint_preview::FileManager;
+
 namespace {
 
 constexpr char kFeatureName[] = "tab_service_test";
@@ -131,6 +134,14 @@
                      key),
       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
   task_environment()->RunUntilIdle();
+
+  service->DeleteAllLongScreenshotFiles();
+  task_environment()->RunUntilIdle();
+  service->GetTaskRunner()->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
+      base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
+  task_environment()->RunUntilIdle();
 }
 
 // Test when PaintPreviewRecorder returns a failure status code.
@@ -148,5 +159,21 @@
         EXPECT_EQ(status, LongScreenshotsTabService::Status::kCaptureFailed);
       }));
   task_environment()->RunUntilIdle();
+
+  auto file_manager = service->GetFileManager();
+  auto key = file_manager->CreateKey(kTabId);
+  service->GetTaskRunner()->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
+      base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
+  task_environment()->RunUntilIdle();
+
+  service->DeleteAllLongScreenshotFiles();
+  task_environment()->RunUntilIdle();
+  service->GetTaskRunner()->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
+      base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
+  task_environment()->RunUntilIdle();
 }
 }  // namespace long_screenshots
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc b/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
index f867b04..9701976 100644
--- a/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
+++ b/chrome/browser/nearby_sharing/common/nearby_share_prefs.cc
@@ -42,6 +42,8 @@
     "nearby_sharing.scheduler.download_device_data";
 const char kNearbySharingSchedulerDownloadPublicCertificatesPrefName[] =
     "nearby_sharing.scheduler.download_public_certificates";
+const char kNearbySharingSchedulerPeriodicContactUploadPrefName[] =
+    "nearby_sharing.scheduler.periodic_contact_upload";
 const char kNearbySharingSchedulerPrivateCertificateExpirationPrefName[] =
     "nearby_sharing.scheduler.private_certificate_expiration";
 const char kNearbySharingSchedulerPublicCertificateExpirationPrefName[] =
@@ -91,6 +93,8 @@
   registry->RegisterDictionaryPref(
       prefs::kNearbySharingSchedulerDownloadPublicCertificatesPrefName);
   registry->RegisterDictionaryPref(
+      prefs::kNearbySharingSchedulerPeriodicContactUploadPrefName);
+  registry->RegisterDictionaryPref(
       prefs::kNearbySharingSchedulerPrivateCertificateExpirationPrefName);
   registry->RegisterDictionaryPref(
       prefs::kNearbySharingSchedulerPublicCertificateExpirationPrefName);
diff --git a/chrome/browser/nearby_sharing/common/nearby_share_prefs.h b/chrome/browser/nearby_sharing/common/nearby_share_prefs.h
index fd95db8..0c1b6fb 100644
--- a/chrome/browser/nearby_sharing/common/nearby_share_prefs.h
+++ b/chrome/browser/nearby_sharing/common/nearby_share_prefs.h
@@ -26,6 +26,7 @@
 extern const char kNearbySharingSchedulerContactDownloadAndUploadPrefName[];
 extern const char kNearbySharingSchedulerDownloadDeviceDataPrefName[];
 extern const char kNearbySharingSchedulerDownloadPublicCertificatesPrefName[];
+extern const char kNearbySharingSchedulerPeriodicContactUploadPrefName[];
 extern const char kNearbySharingSchedulerPrivateCertificateExpirationPrefName[];
 extern const char kNearbySharingSchedulerPublicCertificateExpirationPrefName[];
 extern const char kNearbySharingSchedulerUploadDeviceNamePrefName[];
diff --git a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.cc b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.cc
index 95ff2be..79c06bd7 100644
--- a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.cc
+++ b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.cc
@@ -26,6 +26,7 @@
 
 namespace {
 
+constexpr base::TimeDelta kContactUploadPeriod = base::TimeDelta::FromHours(24);
 constexpr base::TimeDelta kContactDownloadPeriod =
     base::TimeDelta::FromHours(12);
 constexpr base::TimeDelta kContactDownloadRpcTimeout =
@@ -176,6 +177,16 @@
       http_client_factory_(http_client_factory),
       local_device_data_manager_(local_device_data_manager),
       profile_user_name_(profile_user_name),
+      periodic_contact_upload_scheduler_(
+          NearbyShareSchedulerFactory::CreatePeriodicScheduler(
+              kContactUploadPeriod,
+              /*retry_failures=*/false,
+              /*require_connectivity=*/true,
+              prefs::kNearbySharingSchedulerPeriodicContactUploadPrefName,
+              pref_service_,
+              base::BindRepeating(&NearbyShareContactManagerImpl::
+                                      OnPeriodicContactsUploadRequested,
+                                  base::Unretained(this)))),
       contact_download_and_upload_scheduler_(
           NearbyShareSchedulerFactory::CreatePeriodicScheduler(
               kContactDownloadPeriod,
@@ -205,10 +216,12 @@
 }
 
 void NearbyShareContactManagerImpl::OnStart() {
+  periodic_contact_upload_scheduler_->Start();
   contact_download_and_upload_scheduler_->Start();
 }
 
 void NearbyShareContactManagerImpl::OnStop() {
+  periodic_contact_upload_scheduler_->Stop();
   contact_download_and_upload_scheduler_->Stop();
 }
 
@@ -234,6 +247,12 @@
   return allowlist;
 }
 
+void NearbyShareContactManagerImpl::OnPeriodicContactsUploadRequested() {
+  NS_LOG(VERBOSE) << __func__
+                  << ": Periodic Nearby Share contacts upload requested. "
+                  << "Upload will occur after next contacts download.";
+}
+
 void NearbyShareContactManagerImpl::OnContactsDownloadRequested() {
   NS_LOG(VERBOSE) << __func__ << ": Nearby Share contacts download requested.";
 
@@ -279,24 +298,31 @@
     contacts_to_upload.push_back(CreateLocalContact(profile_user_name_));
   }
 
-  // Only request a contacts upload if the contact list or allowlist has changed
-  // since the last successful upload.
   std::string contact_upload_hash = ComputeHash(contacts_to_upload);
-  if (contact_upload_hash ==
-      pref_service_->GetString(
-          prefs::kNearbySharingContactUploadHashPrefName)) {
-    contact_download_and_upload_scheduler_->HandleResult(/*success=*/true);
+  bool did_contacts_change_since_last_upload =
+      contact_upload_hash !=
+      pref_service_->GetString(prefs::kNearbySharingContactUploadHashPrefName);
+  if (did_contacts_change_since_last_upload) {
+    NS_LOG(VERBOSE) << __func__
+                    << ": Contact list or allowlist changed since last "
+                    << "successful upload to the Nearby Share server.";
+  }
+
+  // Request a contacts upload if the contact list or allowlist has changed
+  // since the last successful upload. Also request an upload periodically.
+  if (did_contacts_change_since_last_upload ||
+      periodic_contact_upload_scheduler_->IsWaitingForResult()) {
+    local_device_data_manager_->UploadContacts(
+        std::move(contacts_to_upload),
+        base::BindOnce(&NearbyShareContactManagerImpl::OnContactsUploadFinished,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       did_contacts_change_since_last_upload,
+                       contact_upload_hash));
     return;
   }
 
-  NS_LOG(VERBOSE) << __func__
-                  << ": Contact list or allowlist changed since last "
-                  << "successful upload to the Nearby Share server. "
-                  << "Starting contacts upload.";
-  local_device_data_manager_->UploadContacts(
-      std::move(contacts_to_upload),
-      base::BindOnce(&NearbyShareContactManagerImpl::OnContactsUploadFinished,
-                     weak_ptr_factory_.GetWeakPtr(), contact_upload_hash));
+  // No upload is needed.
+  contact_download_and_upload_scheduler_->HandleResult(/*success=*/true);
 }
 
 void NearbyShareContactManagerImpl::OnContactsDownloadFailure() {
@@ -313,15 +339,24 @@
 }
 
 void NearbyShareContactManagerImpl::OnContactsUploadFinished(
+    bool did_contacts_change_since_last_upload,
     const std::string& contact_upload_hash,
     bool success) {
   NS_LOG(VERBOSE) << __func__ << ": Upload of contacts to Nearby Share server "
                   << (success ? "succeeded." : "failed.")
                   << " Contact upload hash: " << contact_upload_hash;
   if (success) {
+    // Only resolve the periodic upload request on success; let the
+    // download-and-upload scheduler handle any failure retries. The periodic
+    // upload scheduler will remember that it has an outstanding request even
+    // after reboot.
+    if (periodic_contact_upload_scheduler_->IsWaitingForResult()) {
+      periodic_contact_upload_scheduler_->HandleResult(success);
+    }
+
     pref_service_->SetString(prefs::kNearbySharingContactUploadHashPrefName,
                              contact_upload_hash);
-    NotifyContactsUploaded(/*did_contacts_change_since_last_upload=*/true);
+    NotifyContactsUploaded(did_contacts_change_since_last_upload);
   }
 
   contact_download_and_upload_scheduler_->HandleResult(success);
diff --git a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h
index 1315ab7..d78aa49 100644
--- a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h
+++ b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl.h
@@ -35,7 +35,8 @@
 // certificates accordingly. This implementation persists a hash of the last
 // uploaded contact data, and after every contacts download, a subsequent upload
 // request is made if we detect that the contact list or allowlist has changed
-// since the last successful upload.
+// since the last successful upload. We also schedule periodic contact uploads
+// just in case the server removed the record.
 //
 // In addition to supporting on-demand contact downloads, this implementation
 // periodically checks in with the Nearby Share server to see if the user's
@@ -87,12 +88,14 @@
           observer) override;
 
   std::set<std::string> GetAllowedContacts() const;
+  void OnPeriodicContactsUploadRequested();
   void OnContactsDownloadRequested();
   void OnContactsDownloadSuccess(
       std::vector<nearbyshare::proto::ContactRecord> contacts,
       uint32_t num_unreachable_contacts_filtered_out);
   void OnContactsDownloadFailure();
-  void OnContactsUploadFinished(const std::string& contact_upload_hash,
+  void OnContactsUploadFinished(bool did_contacts_change_since_last_upload,
+                                const std::string& contact_upload_hash,
                                 bool success);
   bool SetAllowlist(const std::set<std::string>& new_allowlist);
   void NotifyMojoObserverContactsDownloaded(
@@ -104,6 +107,7 @@
   NearbyShareClientFactory* http_client_factory_ = nullptr;
   NearbyShareLocalDeviceDataManager* local_device_data_manager_ = nullptr;
   std::string profile_user_name_;
+  std::unique_ptr<NearbyShareScheduler> periodic_contact_upload_scheduler_;
   std::unique_ptr<NearbyShareScheduler> contact_download_and_upload_scheduler_;
   std::unique_ptr<NearbyShareContactDownloader> contact_downloader_;
   mojo::RemoteSet<nearby_share::mojom::DownloadContactsObserver> observers_set_;
diff --git a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl_unittest.cc b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl_unittest.cc
index 29c93f7..c3cbbf0 100644
--- a/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl_unittest.cc
@@ -38,6 +38,7 @@
 const char kTestProfileUserName[] = "test@google.com";
 
 // From nearby_share_contact_manager_impl.cc.
+constexpr base::TimeDelta kContactUploadPeriod = base::TimeDelta::FromHours(24);
 constexpr base::TimeDelta kContactDownloadPeriod =
     base::TimeDelta::FromHours(12);
 constexpr base::TimeDelta kContactDownloadRpcTimeout =
@@ -207,7 +208,7 @@
       const std::vector<nearbyshare::proto::ContactRecord>& contacts,
       const std::set<std::string>& expected_allowed_contact_ids,
       bool expect_upload) {
-    TriggerScheduler();
+    TriggerDownloadScheduler();
 
     size_t num_handled_results =
         download_and_upload_scheduler()->handled_results().size();
@@ -243,7 +244,7 @@
   }
 
   void FailDownload() {
-    TriggerScheduler();
+    TriggerDownloadScheduler();
 
     // Fail download and verify that the result is sent to the scheduler.
     size_t num_handled_results =
@@ -259,6 +260,11 @@
     EXPECT_TRUE(mojo_observer_.on_contacts_download_failed_called_);
   }
 
+  void MakePeriodicUploadRequest() {
+    periodic_upload_scheduler()->InvokeRequestCallback();
+    periodic_upload_scheduler()->SetIsWaitingForResult(true);
+  }
+
   void FinishUpload(
       bool success,
       const std::vector<nearbyshare::proto::Contact>& expected_contacts) {
@@ -272,8 +278,10 @@
 
     // Invoke upload callback from local device data manager.
     size_t num_upload_notifications = contacts_uploaded_notifications_.size();
-    size_t num_handled_results =
+    size_t num_download_and_upload_handled_results =
         download_and_upload_scheduler()->handled_results().size();
+    size_t num_periodic_upload_handeled_results =
+        periodic_upload_scheduler()->handled_results().size();
     std::move(call.callback).Run(success);
 
     // Verify upload notification was sent on success.
@@ -281,13 +289,24 @@
               contacts_uploaded_notifications_.size());
     if (success) {
       // We only expect uploads to occur if contacts have changed since the last
-      // upload.
+      // upload or is a periodic upload was requested.
       EXPECT_TRUE(contacts_uploaded_notifications_.back()
-                      .did_contacts_change_since_last_upload);
+                      .did_contacts_change_since_last_upload ||
+                  periodic_upload_scheduler()->IsWaitingForResult());
+
+      if (periodic_upload_scheduler()->IsWaitingForResult()) {
+        EXPECT_EQ(num_periodic_upload_handeled_results + 1,
+                  periodic_upload_scheduler()->handled_results().size());
+        EXPECT_TRUE(periodic_upload_scheduler()->handled_results().back());
+        periodic_upload_scheduler()->SetIsWaitingForResult(false);
+      } else {
+        EXPECT_EQ(num_periodic_upload_handeled_results,
+                  periodic_upload_scheduler()->handled_results().size());
+      }
     }
 
     // Verify that result is sent to download/upload scheduler.
-    EXPECT_EQ(num_handled_results + 1,
+    EXPECT_EQ(num_download_and_upload_handled_results + 1,
               download_and_upload_scheduler()->handled_results().size());
     EXPECT_EQ(success,
               download_and_upload_scheduler()->handled_results().back());
@@ -330,14 +349,20 @@
     return downloader_factory_.instances().back();
   }
 
+  FakeNearbyShareScheduler* periodic_upload_scheduler() {
+    return scheduler_factory_.pref_name_to_periodic_instance()
+        .at(prefs::kNearbySharingSchedulerPeriodicContactUploadPrefName)
+        .fake_scheduler;
+  }
+
   FakeNearbyShareScheduler* download_and_upload_scheduler() {
     return scheduler_factory_.pref_name_to_periodic_instance()
         .at(prefs::kNearbySharingSchedulerContactDownloadAndUploadPrefName)
         .fake_scheduler;
   }
 
+  // Verify scheduler input parameters.
   void VerifySchedulerInitialization() {
-    // Verify scheduler input parameters.
     FakeNearbyShareSchedulerFactory::PeriodicInstance
         download_and_upload_scheduler_instance =
             scheduler_factory_.pref_name_to_periodic_instance().at(
@@ -349,9 +374,20 @@
     EXPECT_TRUE(download_and_upload_scheduler_instance.require_connectivity);
     EXPECT_EQ(&pref_service_,
               download_and_upload_scheduler_instance.pref_service);
+
+    FakeNearbyShareSchedulerFactory::PeriodicInstance
+        periodic_upload_scheduler_instance =
+            scheduler_factory_.pref_name_to_periodic_instance().at(
+                prefs::kNearbySharingSchedulerPeriodicContactUploadPrefName);
+    EXPECT_TRUE(periodic_upload_scheduler_instance.fake_scheduler);
+    EXPECT_EQ(kContactUploadPeriod,
+              periodic_upload_scheduler_instance.request_period);
+    EXPECT_FALSE(periodic_upload_scheduler_instance.retry_failures);
+    EXPECT_TRUE(periodic_upload_scheduler_instance.require_connectivity);
+    EXPECT_EQ(&pref_service_, periodic_upload_scheduler_instance.pref_service);
   }
 
-  void TriggerScheduler() {
+  void TriggerDownloadScheduler() {
     // Fire scheduler and verify downloader creation.
     size_t num_downloaders = downloader_factory_.instances().size();
     download_and_upload_scheduler()->InvokeRequestCallback();
@@ -452,7 +488,7 @@
                      /*expect_allowlist_changed=*/false);
 }
 
-TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_WithUpload) {
+TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_WithFirstUpload) {
   std::vector<nearbyshare::proto::ContactRecord> contact_records =
       TestContactRecordList(/*num_contacts=*/3u);
   std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
@@ -518,6 +554,32 @@
                    allowlist, contact_records));
 }
 
+TEST_F(NearbyShareContactManagerImplTest,
+       DownloadContacts_PeriodicUploadRequest) {
+  std::vector<nearbyshare::proto::ContactRecord> contact_records =
+      TestContactRecordList(/*num_contacts=*/3u);
+  std::set<std::string> allowlist = TestContactIds(/*num_contacts=*/2u);
+  SetAllowedContacts(allowlist, /*expect_allowlist_changed=*/true);
+
+  // Because contacts have never been uploaded, a subsequent upload is
+  // requested, which succeeds.
+  DownloadContacts();
+  SucceedDownload(contact_records, allowlist, /*expect_upload=*/true);
+  FinishUpload(/*success=*/true, /*expected_contacts=*/BuildContactListToUpload(
+                   allowlist, contact_records));
+
+  // Because device records on the server will be removed after a few days if
+  // the device does not contact the server, we ensure that contacts are
+  // uploaded periodically. Make that request now. Contacts will be uploaded
+  // after the next contact download. It will not force a download now, however.
+  MakePeriodicUploadRequest();
+
+  // When contacts are downloaded again, we decect that contacts have not
+  // changed. However, we expect an upload because a periodic request was made.
+  DownloadContacts();
+  SucceedDownload(contact_records, allowlist, /*expect_upload=*/true);
+}
+
 TEST_F(NearbyShareContactManagerImplTest, DownloadContacts_FailDownload) {
   DownloadContacts();
   FailDownload();
diff --git a/chrome/browser/password_manager/password_manager_interactive_uitest.cc b/chrome/browser/password_manager/password_manager_interactive_uitest.cc
index fd3bdef..e5591047 100644
--- a/chrome/browser/password_manager/password_manager_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_manager_interactive_uitest.cc
@@ -315,6 +315,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
+// Tests that submission is detected when change password form is reset.
 IN_PROC_BROWSER_TEST_F(
     PasswordManagerInteractiveTestSubmissionDetectionOnFormClear,
     ChangePwdFormCleared) {
@@ -331,7 +332,7 @@
   signin_form.password_value = base::ASCIIToUTF16("old_pw");
   password_store->AddLogin(signin_form);
 
-  NavigateToFile("/password/password_form.html");
+  NavigateToFile("/password/cleared_change_password_forms.html");
 
   // Fill a form and submit through a <input type="submit"> button.
   std::unique_ptr<BubbleObserver> prompt_observer(
@@ -358,6 +359,125 @@
       1);
 }
 
+// Tests that submission is detected when all password fields in a change
+// password form are cleared and not detected when only some fields are cleared.
+IN_PROC_BROWSER_TEST_F(
+    PasswordManagerInteractiveTestSubmissionDetectionOnFormClear,
+    ChangePwdFormFieldsCleared) {
+  // At first let us save credentials to the PasswordManager.
+  scoped_refptr<password_manager::TestPasswordStore> password_store =
+      static_cast<password_manager::TestPasswordStore*>(
+          PasswordStoreFactory::GetForProfile(
+              browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
+              .get());
+  password_manager::PasswordForm signin_form;
+  signin_form.signon_realm = embedded_test_server()->base_url().spec();
+  signin_form.username_value = base::ASCIIToUTF16("temp");
+  signin_form.password_value = base::ASCIIToUTF16("old_pw");
+  password_store->AddLogin(signin_form);
+
+  for (bool all_fields_cleared : {false, true}) {
+    base::HistogramTester histogram_tester;
+    SCOPED_TRACE(testing::Message("#all_fields_cleared = ")
+                 << all_fields_cleared);
+    NavigateToFile("/password/cleared_change_password_forms.html");
+
+    // Fill a form and submit through a <input type="submit"> button.
+    std::unique_ptr<BubbleObserver> prompt_observer(
+        new BubbleObserver(WebContents()));
+
+    FillElementWithValue("chg_new_password_1", "new_pw", "new_pw");
+    FillElementWithValue("chg_new_password_2", "new_pw", "new_pw");
+
+    std::string submit =
+        all_fields_cleared
+            ? "document.getElementById('chg_clear_all_fields_button').click();"
+            : "document.getElementById('chg_clear_some_fields_button').click()"
+              ";";
+    ASSERT_TRUE(content::ExecuteScript(WebContents(), submit));
+
+    if (all_fields_cleared)
+      EXPECT_TRUE(prompt_observer->IsUpdatePromptShownAutomatically());
+    else
+      EXPECT_FALSE(prompt_observer->IsUpdatePromptShownAutomatically());
+
+    if (all_fields_cleared) {
+      // We emulate that the user clicks "Update" button.
+      prompt_observer->AcceptUpdatePrompt();
+
+      // Check that credentials are stored.
+      WaitForPasswordStore();
+      CheckThatCredentialsStored("temp", "new_pw");
+      histogram_tester.ExpectUniqueSample(
+          "PasswordManager.SuccessfulSubmissionIndicatorEvent",
+          autofill::mojom::SubmissionIndicatorEvent::
+              CHANGE_PASSWORD_FORM_CLEARED,
+          1);
+    }
+  }
+}
+
+// Tests that submission is detected when the new password field outside the
+// form tag is cleared not detected when other password fields are cleared.
+IN_PROC_BROWSER_TEST_F(
+    PasswordManagerInteractiveTestSubmissionDetectionOnFormClear,
+    ChangePwdFormRelevantFormlessFieldsCleared) {
+  base::HistogramTester histogram_tester;
+  // At first let us save credentials to the PasswordManager.
+  scoped_refptr<password_manager::TestPasswordStore> password_store =
+      static_cast<password_manager::TestPasswordStore*>(
+          PasswordStoreFactory::GetForProfile(
+              browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS)
+              .get());
+  password_manager::PasswordForm signin_form;
+  signin_form.signon_realm = embedded_test_server()->base_url().spec();
+  signin_form.username_value = base::ASCIIToUTF16("temp");
+  signin_form.password_value = base::ASCIIToUTF16("old_pw");
+  password_store->AddLogin(signin_form);
+
+  for (bool relevant_fields_cleared : {false, true}) {
+    SCOPED_TRACE(testing::Message("#relevant_fields_cleared = ")
+                 << relevant_fields_cleared);
+    NavigateToFile("/password/cleared_change_password_forms.html");
+
+    // Fill a form and submit through a <input type="submit"> button.
+    std::unique_ptr<BubbleObserver> prompt_observer(
+        new BubbleObserver(WebContents()));
+
+    FillElementWithValue("formless_chg_new_password_1", "new_pw", "new_pw");
+    FillElementWithValue("formless_chg_new_password_2", "new_pw", "new_pw");
+
+    std::string submit = relevant_fields_cleared
+                             ? "document.getElementById('chg_clear_all_"
+                               "formless_fields_button').click();"
+                             : "document.getElementById('chg_clear_some_"
+                               "formless_fields_button').click();";
+
+    ASSERT_TRUE(content::ExecuteScript(WebContents(), submit));
+
+    if (relevant_fields_cleared) {
+      prompt_observer->WaitForAutomaticUpdatePrompt();
+      EXPECT_TRUE(prompt_observer->IsUpdatePromptShownAutomatically());
+    } else {
+      EXPECT_FALSE(prompt_observer->IsUpdatePromptShownAutomatically());
+    }
+
+    if (relevant_fields_cleared) {
+      // We emulate that the user clicks "Update" button.
+      prompt_observer->AcceptUpdatePrompt();
+
+      // Check that credentials are stored.
+      WaitForPasswordStore();
+      CheckThatCredentialsStored("temp", "new_pw");
+      histogram_tester.ExpectUniqueSample(
+          "PasswordManager.SuccessfulSubmissionIndicatorEvent",
+          autofill::mojom::SubmissionIndicatorEvent::
+              CHANGE_PASSWORD_FORM_CLEARED,
+          1);
+    }
+  }
+}
+
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 // This test suite only applies to Gaia signin page, and checks that the
 // signin interception bubble and the password bubbles never conflict.
diff --git a/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.cc b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.cc
new file mode 100644
index 0000000..3945acb
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.cc
@@ -0,0 +1,78 @@
+// 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/performance_manager/decorators/execution_context_priority/frame_visibility_voter.h"
+
+#include <utility>
+
+#include "components/performance_manager/public/execution_context/execution_context_registry.h"
+#include "url/gurl.h"
+
+namespace performance_manager {
+namespace execution_context_priority {
+
+namespace {
+
+const execution_context::ExecutionContext* GetExecutionContext(
+    const FrameNode* frame_node) {
+  return execution_context::ExecutionContextRegistry::GetFromGraph(
+             frame_node->GetGraph())
+      ->GetExecutionContextForFrameNode(frame_node);
+}
+
+// Returns a vote with the appropriate priority depending on the frame's
+// |visibility|.
+Vote GetVote(FrameNode::Visibility visibility) {
+  base::TaskPriority priority;
+  switch (visibility) {
+    case FrameNode::Visibility::kUnknown:
+      priority = base::TaskPriority::USER_VISIBLE;
+      break;
+    case FrameNode::Visibility::kVisible:
+      priority = base::TaskPriority::USER_VISIBLE;
+      break;
+    case FrameNode::Visibility::kNotVisible:
+      priority = base::TaskPriority::LOWEST;
+      break;
+  }
+  return Vote(priority, FrameVisibilityVoter::kFrameVisibilityReason);
+}
+
+}  // namespace
+
+// static
+const char FrameVisibilityVoter::kFrameVisibilityReason[] = "Frame visibility.";
+
+FrameVisibilityVoter::FrameVisibilityVoter() = default;
+
+FrameVisibilityVoter::~FrameVisibilityVoter() = default;
+
+void FrameVisibilityVoter::SetVotingChannel(VotingChannel voting_channel) {
+  voting_channel_.SetVotingChannel(std::move(voting_channel));
+}
+
+void FrameVisibilityVoter::OnFrameNodeAdded(const FrameNode* frame_node) {
+  const Vote vote = GetVote(frame_node->GetVisibility());
+  voting_channel_.SubmitVote(GetExecutionContext(frame_node), vote);
+}
+
+void FrameVisibilityVoter::OnBeforeFrameNodeRemoved(
+    const FrameNode* frame_node) {
+  voting_channel_.InvalidateVote(GetExecutionContext(frame_node));
+}
+
+void FrameVisibilityVoter::OnFrameVisibilityChanged(
+    const FrameNode* frame_node,
+    FrameNode::Visibility previous_value) {
+  const Vote new_vote = GetVote(frame_node->GetVisibility());
+
+  // Nothing to change if the new priority is the same as the old one.
+  if (new_vote == GetVote(previous_value))
+    return;
+
+  voting_channel_.ChangeVote(GetExecutionContext(frame_node), new_vote);
+}
+
+}  // namespace execution_context_priority
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.h b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.h
new file mode 100644
index 0000000..77f9e8a
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter.h
@@ -0,0 +1,45 @@
+// 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_PERFORMANCE_MANAGER_DECORATORS_EXECUTION_CONTEXT_PRIORITY_FRAME_VISIBILITY_VOTER_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_EXECUTION_CONTEXT_PRIORITY_FRAME_VISIBILITY_VOTER_H_
+
+#include "components/performance_manager/public/execution_context_priority/execution_context_priority.h"
+#include "components/performance_manager/public/execution_context_priority/max_vote_aggregator.h"
+#include "components/performance_manager/public/graph/frame_node.h"
+
+namespace performance_manager {
+namespace execution_context_priority {
+
+// This voter tracks frame nodes and casts a vote for each of them, whose value
+// depends on their visibility. A visible frame will receive a
+// TaskPriority::USER_VISIBLE vote, while a non-visible frame will receive a
+// TaskPriority::LOWEST vote.
+class FrameVisibilityVoter : public FrameNode::ObserverDefaultImpl {
+ public:
+  static const char kFrameVisibilityReason[];
+
+  FrameVisibilityVoter();
+  ~FrameVisibilityVoter() override;
+
+  FrameVisibilityVoter(const FrameVisibilityVoter&) = delete;
+  FrameVisibilityVoter& operator=(const FrameVisibilityVoter&) = delete;
+
+  // Sets the voting channel where the votes will be cast.
+  void SetVotingChannel(VotingChannel voting_channel);
+
+  // FrameNodeObserver:
+  void OnFrameNodeAdded(const FrameNode* frame_node) override;
+  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override;
+  void OnFrameVisibilityChanged(const FrameNode* frame_node,
+                                FrameNode::Visibility previous_value) override;
+
+ private:
+  VotingChannelWrapper voting_channel_;
+};
+
+}  // namespace execution_context_priority
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_EXECUTION_CONTEXT_PRIORITY_FRAME_VISIBILITY_VOTER_H_
diff --git a/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter_unittest.cc b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter_unittest.cc
new file mode 100644
index 0000000..8ca2229
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter_unittest.cc
@@ -0,0 +1,119 @@
+// 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/performance_manager/decorators/execution_context_priority/frame_visibility_voter.h"
+
+#include "components/performance_manager/execution_context/execution_context_registry_impl.h"
+#include "components/performance_manager/public/execution_context/execution_context.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/mock_graphs.h"
+#include "components/performance_manager/test_support/voting.h"
+
+namespace performance_manager {
+namespace execution_context_priority {
+
+using DummyVoteObserver = voting::test::DummyVoteObserver<Vote>;
+
+namespace {
+
+const execution_context::ExecutionContext* GetExecutionContext(
+    const FrameNode* frame_node) {
+  return execution_context::ExecutionContextRegistry::GetFromGraph(
+             frame_node->GetGraph())
+      ->GetExecutionContextForFrameNode(frame_node);
+}
+
+// Both the voting channel and the FrameVisibilityVoter are expected live on the
+// graph, without being actual GraphOwned objects. This class wraps both to
+// allow this.
+class GraphOwnedWrapper : public GraphOwned {
+ public:
+  GraphOwnedWrapper() {
+    VotingChannel voting_channel = observer_.BuildVotingChannel();
+    voter_id_ = voting_channel.voter_id();
+    frame_visibility_voter_.SetVotingChannel(std::move(voting_channel));
+  }
+
+  ~GraphOwnedWrapper() override = default;
+
+  GraphOwnedWrapper(const GraphOwnedWrapper&) = delete;
+  GraphOwnedWrapper& operator=(const GraphOwnedWrapper&) = delete;
+
+  // GraphOwned:
+  void OnPassedToGraph(Graph* graph) override {
+    graph->AddFrameNodeObserver(&frame_visibility_voter_);
+  }
+  void OnTakenFromGraph(Graph* graph) override {
+    graph->RemoveFrameNodeObserver(&frame_visibility_voter_);
+  }
+
+  // Exposes the DummyVoteObserver to validate expectations.
+  const DummyVoteObserver& observer() const { return observer_; }
+
+  VoterId voter_id() const { return voter_id_; }
+
+ private:
+  DummyVoteObserver observer_;
+  FrameVisibilityVoter frame_visibility_voter_;
+  VoterId voter_id_;
+};
+
+}  // namespace
+
+class FrameVisibilityVoterTest : public GraphTestHarness {
+ public:
+  FrameVisibilityVoterTest() = default;
+  ~FrameVisibilityVoterTest() override = default;
+
+  FrameVisibilityVoterTest(const FrameVisibilityVoterTest&) = delete;
+  FrameVisibilityVoterTest& operator=(const FrameVisibilityVoterTest&) = delete;
+
+  void SetUp() override {
+    graph()->PassToGraph(
+        std::make_unique<execution_context::ExecutionContextRegistryImpl>());
+
+    auto wrapper = std::make_unique<GraphOwnedWrapper>();
+    wrapper_ = wrapper.get();
+    graph()->PassToGraph(std::move(wrapper));
+  }
+
+  // Exposes the DummyVoteObserver to validate expectations.
+  const DummyVoteObserver& observer() const { return wrapper_->observer(); }
+
+  VoterId voter_id() const { return wrapper_->voter_id(); }
+
+ private:
+  GraphOwnedWrapper* wrapper_ = nullptr;
+};
+
+// Tests that the FrameVisibilityVoter correctly casts a vote for a frame
+// depending on its visibility.
+TEST_F(FrameVisibilityVoterTest, ChangeFrameVisibility) {
+  // Create a graph with a single frame in a non-visible page. Its initial
+  // visibility should be kNotVisible, resulting in a low priority.
+  MockSinglePageInSingleProcessGraph mock_graph(graph());
+  auto& frame_node = mock_graph.frame;
+  EXPECT_EQ(frame_node->visibility(), FrameNode::Visibility::kNotVisible);
+  EXPECT_EQ(observer().GetVoteCount(), 1u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::LOWEST,
+                                 FrameVisibilityVoter::kFrameVisibilityReason));
+
+  // Make the frame visible. This should increase the priority.
+  mock_graph.frame->SetVisibility(FrameNode::Visibility::kVisible);
+  EXPECT_EQ(observer().GetVoteCount(), 1u);
+  EXPECT_TRUE(observer().HasVote(voter_id(),
+                                 GetExecutionContext(frame_node.get()),
+                                 base::TaskPriority::USER_VISIBLE,
+                                 FrameVisibilityVoter::kFrameVisibilityReason));
+
+  // Deleting the frame should invalidate the vote.
+  frame_node.reset();
+  EXPECT_EQ(observer().GetVoteCount(), 0u);
+}
+
+}  // namespace execution_context_priority
+}  // namespace performance_manager
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index 661f68b..f018dca 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/location.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/net/prediction_options.h"
 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
 #include "chrome/browser/prefetch/search_prefetch/full_body_search_prefetch_request.h"
@@ -15,8 +16,12 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
+#include "chrome/common/pref_names.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "components/omnibox/browser/base_search_provider.h"
+#include "components/prefs/pref_service.h"
 #include "components/search_engines/template_url_service.h"
 #include "url/origin.h"
 
@@ -58,6 +63,19 @@
     return false;
   }
 
+  if (!profile_->GetPrefs() ||
+      !profile_->GetPrefs()->GetBoolean(prefs::kWebKitJavascriptEnabled)) {
+    return false;
+  }
+
+  auto* content_settings =
+      HostContentSettingsMapFactory::GetForProfile(profile_);
+  if (!content_settings ||
+      content_settings->GetContentSetting(
+          url, url, ContentSettingsType::JAVASCRIPT) == CONTENT_SETTING_BLOCK) {
+    return false;
+  }
+
   auto* template_url_service =
       TemplateURLServiceFactory::GetForProfile(profile_);
   if (!template_url_service ||
@@ -137,6 +155,20 @@
       !template_url_service->GetDefaultSearchProvider())
     return nullptr;
 
+  // The user may have disabled JS since the prefetch occured.
+  if (!profile_->GetPrefs() ||
+      !profile_->GetPrefs()->GetBoolean(prefs::kWebKitJavascriptEnabled)) {
+    return nullptr;
+  }
+
+  auto* content_settings =
+      HostContentSettingsMapFactory::GetForProfile(profile_);
+  if (!content_settings ||
+      content_settings->GetContentSetting(
+          url, url, ContentSettingsType::JAVASCRIPT) == CONTENT_SETTING_BLOCK) {
+    return nullptr;
+  }
+
   base::string16 search_terms;
   template_url_service->GetDefaultSearchProvider()->ExtractSearchTermsFromURL(
       url, template_url_service->search_terms_data(), &search_terms);
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 4241d4f..092423e 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/net/prediction_options.h"
 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
 #include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
@@ -21,6 +22,8 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_provider.h"
@@ -49,6 +52,7 @@
 constexpr char kOmniboxSuggestPrefetchQuery[] = "porgs";
 constexpr char kOmniboxSuggestPrefetchSecondItemQuery[] = "porgsandwich";
 constexpr char kOmniboxSuggestNonPrefetchQuery[] = "puffins";
+constexpr char kLoadInSubframe[] = "/load_in_subframe";
 }  // namespace
 
 // A response that hangs after serving the start of the response.
@@ -127,6 +131,14 @@
     return search_server_->GetURL(kSearchDomain, path);
   }
 
+  // Get a URL for a page that embeds the search |path| as an iframe.
+  GURL GetSearchServerQueryURLWithSubframeLoad(const std::string& path) const {
+    return search_server_->GetURL(kSearchDomain,
+                                  std::string(kLoadInSubframe)
+                                      .append("/search_page.html?q=")
+                                      .append(path));
+  }
+
   GURL GetSuggestServerURL(const std::string& path) const {
     return search_suggest_server_->GetURL(kSuggestDomain, path);
   }
@@ -258,6 +270,23 @@
                            MonitorSearchResourceRequestOnUIThread,
                        base::Unretained(this), request, is_prefetch));
 
+    // If this is an embedded search for load in iframe, parse out the iframe
+    // URL and serve it as an iframe in the returned HTML.
+    if (request.relative_url.find(kLoadInSubframe) == 0) {
+      std::string subframe_path =
+          request.relative_url.substr(std::string(kLoadInSubframe).size());
+      std::string content = "<html><body><iframe src=\"";
+      content.append(subframe_path);
+      content.append("\"/></body></html>");
+
+      std::unique_ptr<net::test_server::BasicHttpResponse> resp =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      resp->set_code(is_prefetch ? net::HTTP_BAD_GATEWAY : net::HTTP_OK);
+      resp->set_content_type("text/html");
+      resp->set_content(content);
+      return resp;
+    }
+
     if (request.GetURL().spec().find("502_on_prefetch") != std::string::npos) {
       std::unique_ptr<net::test_server::BasicHttpResponse> resp =
           std::make_unique<net::test_server::BasicHttpResponse>();
@@ -1043,6 +1072,113 @@
 }
 
 IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       NoPrefetchWhenJSDisabled) {
+  browser()->profile()->GetPrefs()->SetBoolean(prefs::kWebKitJavascriptEnabled,
+                                               false);
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms);
+
+  EXPECT_FALSE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+}
+
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       NoPrefetchWhenJSDisabledOnDSE) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms);
+
+  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
+      ->SetContentSettingDefaultScope(prefetch_url, GURL(),
+                                      ContentSettingsType::JAVASCRIPT,
+                                      CONTENT_SETTING_BLOCK);
+
+  EXPECT_FALSE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  EXPECT_FALSE(prefetch_status.has_value());
+}
+
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       NoServeWhenJSDisabled) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms);
+
+  EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kInFlight, prefetch_status.value());
+
+  WaitUntilStatusChanges(base::ASCIIToUTF16(search_terms));
+
+  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
+      base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
+  browser()->profile()->GetPrefs()->SetBoolean(prefs::kWebKitJavascriptEnabled,
+                                               false);
+
+  ui_test_utils::NavigateToURL(browser(), prefetch_url);
+
+  // The prefetch request and the new non-prefetched served request.
+  EXPECT_EQ(2u, search_server_request_count());
+}
+
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       NoServeWhenJSDisabledOnDSE) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms);
+
+  EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kInFlight, prefetch_status.value());
+
+  WaitUntilStatusChanges(base::ASCIIToUTF16(search_terms));
+
+  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
+      base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
+
+  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
+      ->SetContentSettingDefaultScope(prefetch_url, GURL(),
+                                      ContentSettingsType::JAVASCRIPT,
+                                      CONTENT_SETTING_BLOCK);
+
+  ui_test_utils::NavigateToURL(browser(), prefetch_url);
+
+  // The prefetch request and the new non-prefetched served request.
+  EXPECT_EQ(2u, search_server_request_count());
+}
+
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
                        OnlyStreamedResponseCanServePartialRequest) {
   set_hang_requests_after_start(true);
   auto* search_prefetch_service =
@@ -1076,6 +1212,60 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
+                       DontInterceptSubframes) {
+  auto* search_prefetch_service =
+      SearchPrefetchServiceFactory::GetForProfile(browser()->profile());
+  EXPECT_NE(nullptr, search_prefetch_service);
+
+  std::string search_terms = "prefetch_content";
+  GURL prefetch_url = GetSearchServerQueryURL(search_terms);
+  GURL navigation_url = GetSearchServerQueryURLWithSubframeLoad(search_terms);
+
+  EXPECT_TRUE(search_prefetch_service->MaybePrefetchURL(prefetch_url));
+  auto prefetch_status =
+      search_prefetch_service->GetSearchPrefetchStatusForTesting(
+          base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kInFlight, prefetch_status.value());
+
+  WaitUntilStatusChanges(base::ASCIIToUTF16(search_terms));
+
+  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
+      base::ASCIIToUTF16(search_terms));
+  ASSERT_TRUE(prefetch_status.has_value());
+  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
+
+  ui_test_utils::NavigateToURL(browser(), navigation_url);
+
+  const auto& requests = search_server_requests();
+  EXPECT_EQ(3u, requests.size());
+  // This flow should have resulted in a prefetch of the search terms, a main
+  // frame navigation to the special subframe loader page, and a navigation to
+  // the subframe that matches the prefetch URL.
+
+  // 2 requests should be to the search terms directly, one for the prefetch and
+  // one for the subframe (that can't be served from the prefetch cache).
+  EXPECT_EQ(2,
+            std::count_if(requests.begin(), requests.end(),
+                          [search_terms](const auto& request) {
+                            return request.relative_url.find(kLoadInSubframe) ==
+                                       std::string::npos &&
+                                   request.relative_url.find(search_terms) !=
+                                       std::string::npos;
+                          }));
+  // 1 request should specify to load content in a subframe but also contain the
+  // search terms.
+  EXPECT_EQ(1,
+            std::count_if(requests.begin(), requests.end(),
+                          [search_terms](const auto& request) {
+                            return request.relative_url.find(kLoadInSubframe) !=
+                                       std::string::npos &&
+                                   request.relative_url.find(search_terms) !=
+                                       std::string::npos;
+                          }));
+}
+
 // True means that responses are streamed, false means full responses must be
 // received in order to server the response.
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc
index 6abfa3f..90a16eb 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/no_state_prefetch/browser/prerender_manager.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 
@@ -47,6 +48,21 @@
 
   DCHECK(!loader_callback_);
   loader_callback_ = std::move(callback);
+
+  content::WebContents* web_contents =
+      content::WebContents::FromFrameTreeNodeId(frame_tree_node_id_);
+  // Make sure this is for a navigation.
+  if (!web_contents) {
+    DoNotInterceptPrefetchedNavigation();
+    return;
+  }
+  // Only intercept main frame requests.
+  content::RenderFrameHost* main_frame = web_contents->GetMainFrame();
+  if (!main_frame || main_frame->GetFrameTreeNodeId() != frame_tree_node_id_) {
+    DoNotInterceptPrefetchedNavigation();
+    return;
+  }
+
   url_ = tentative_resource_request.url;
 
   std::unique_ptr<SearchPrefetchURLLoader> prefetch =
diff --git a/chrome/browser/referrer_policy_browsertest.cc b/chrome/browser/referrer_policy_browsertest.cc
index 946cd1d..a3fdfc5 100644
--- a/chrome/browser/referrer_policy_browsertest.cc
+++ b/chrome/browser/referrer_policy_browsertest.cc
@@ -788,7 +788,6 @@
             ReferrerPolicyTest::EXPECT_ORIGIN_AS_REFERRER,
     },
     {
-        .feature_to_enable = blink::features::kReducedReferrerGranularity,
         .baseline_policy = network::mojom::ReferrerPolicy::kDefault,
         // kDefault gets resolved into a concrete policy when making requests
         .expected_policy =
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index f55d617..0bb292c 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -800,10 +800,7 @@
 
   // Set up referrer URL with fragment.
   const GURL kReferrerWithFragment("http://foo.com/test#fragment");
-  const std::string kCorrectReferrer(
-      base::FeatureList::IsEnabled(blink::features::kReducedReferrerGranularity)
-          ? "http://foo.com/"
-          : "http://foo.com/test");
+  const std::string kCorrectReferrer("http://foo.com/");
 
   // Set up menu with link URL.
   content::ContextMenuParams context_menu_params;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
index 0bad457..2a31c3a 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
@@ -5,6 +5,9 @@
 var AutomationEvent = chrome.automation.AutomationEvent;
 var EventType = chrome.automation.EventType;
 var RoleType = chrome.automation.RoleType;
+const AccessibilityFeature = chrome.accessibilityPrivate.AccessibilityFeature;
+const SelectToSpeakPanelAction =
+    chrome.accessibilityPrivate.SelectToSpeakPanelAction;
 
 // This must be the same as in ash/system/accessibility/select_to_speak_tray.cc:
 // ash::kSelectToSpeakTrayClassName.
@@ -138,6 +141,18 @@
         'enable-experimental-accessibility-language-detection', (result) => {
           this.enableLanguageDetectionIntegration_ = result;
         });
+
+    /**
+     * Feature flag controlling STS navigation control.
+     * @type {boolean}
+     */
+    this.enableNavigationControl_ = false;
+
+    // TODO(crbug.com/1143830): Also check STS client-side setting.
+    chrome.accessibilityPrivate.isFeatureEnabled(
+        AccessibilityFeature.SELECT_TO_SPEAK_NAVIGATION_CONTROL, (result) => {
+          this.enableNavigationControl_ = result;
+        });
   }
 
   /**
@@ -433,6 +448,9 @@
     this.setFocusRings_([], false /* do not draw background */);
     chrome.accessibilityPrivate.setHighlights(
         [], this.prefsManager_.highlightColor());
+    if (this.enableNavigationControl_) {
+      chrome.accessibilityPrivate.updateSelectToSpeakPanel(/* show= */ false);
+    }
   }
 
   /**
@@ -527,6 +545,8 @@
     this.inputHandler_.setUpEventListeners();
     chrome.accessibilityPrivate.onSelectToSpeakStateChangeRequested.addListener(
         this.onStateChangeRequested_.bind(this));
+    chrome.accessibilityPrivate.onSelectToSpeakPanelAction.addListener(
+        this.onSelectToSpeakPanelAction_.bind(this));
     // Initialize the state to SelectToSpeakState.INACTIVE.
     chrome.accessibilityPrivate.setSelectToSpeakState(this.state_);
   }
@@ -565,6 +585,25 @@
   }
 
   /**
+   * Handles Select-to-speak panel action.
+   * @param {!SelectToSpeakPanelAction} panelAction
+   * @private
+   */
+  onSelectToSpeakPanelAction_(panelAction) {
+    if (!this.enableNavigationControl_) {
+      // Ignore if this feature is not enabled.
+      return;
+    }
+    switch (panelAction) {
+      case SelectToSpeakPanelAction.EXIT:
+        this.stopAll_();
+        break;
+      default:
+        // TODO(crbug.com/1140216): Implement other actions.
+    }
+  }
+
+  /**
    * Enqueue speech for the single given string. The string is not associated
    * with any particular nodes, so this does not do any work around drawing
    * focus rings, unlike startSpeechQueue_ below.
@@ -579,8 +618,11 @@
         this.onStateChanged_(SelectToSpeakState.SPEAKING);
         this.testCurrentNode_();
       } else if (
-          event.type === 'end' || event.type === 'interrupted' ||
-          event.type === 'cancelled') {
+          (event.type === 'end' || event.type === 'interrupted' ||
+           event.type === 'cancelled') &&
+          !this.enableNavigationControl_) {
+        // Automatically dismiss when we're at the end, unless navigation
+        // controled is enabled, in which case we persist STS.
         this.onStateChanged_(SelectToSpeakState.INACTIVE);
       }
     };
@@ -693,7 +735,9 @@
         } else if (event.type === 'interrupted' || event.type === 'cancelled') {
           this.onStateChanged_(SelectToSpeakState.INACTIVE);
         } else if (event.type === 'end') {
-          if (isLast) {
+          if (isLast && !this.enableNavigationControl_) {
+            // Auto dismiss when we're at the end, unless navigation control
+            // is enabled.
             this.onStateChanged_(SelectToSpeakState.INACTIVE);
           }
         } else if (event.type === 'word') {
@@ -946,12 +990,19 @@
     // blocks.
     // TODO: Better test: has no siblings in the group, highlight just
     // the one node. if it has siblings, highlight the parent.
-    if (this.currentBlockParent_ != null &&
+    let focusRingRect;
+    if (this.currentBlockParent_ !== null &&
         node.role === RoleType.INLINE_TEXT_BOX) {
-      this.setFocusRings_(
-          [this.currentBlockParent_.location], true /* draw background */);
+      focusRingRect = this.currentBlockParent_.location;
     } else {
-      this.setFocusRings_([node.location], true /* draw background */);
+      focusRingRect = node.location;
+    }
+    this.setFocusRings_([focusRingRect], true /* draw background */);
+    if (this.enableNavigationControl_) {
+      // TODO(crbug.com/1143817): Update paused state correctly once
+      // pause/resume functionality is implemented.
+      chrome.accessibilityPrivate.updateSelectToSpeakPanel(
+          /* show= */ true, /* anchor= */ focusRingRect, /* isPaused= */ false);
     }
   }
 
diff --git a/chrome/browser/resources/nearby_share/nearby_device.html b/chrome/browser/resources/nearby_share/nearby_device.html
index 3d477918..d1d76b3 100644
--- a/chrome/browser/resources/nearby_share/nearby_device.html
+++ b/chrome/browser/resources/nearby_share/nearby_device.html
@@ -1,6 +1,7 @@
 <style>
   #wrapper {
     align-items: center;
+    background-color: rgb(255, 255, 255);
     border: 1px solid rgba(216, 216, 216, 0.76);
     border-radius: 8px;
     box-sizing: border-box;
@@ -19,11 +20,11 @@
   }
 
   :host(:focus) #wrapper {
-    border-color: rgb(95, 99, 104);
+    border-color: var(--google-grey-refresh-700);
   }
 
   :host([is-selected]) #wrapper {
-    border-color: rgb(66, 133, 244);
+    border-color: var(--google-blue-refresh-500);
   }
 
   :host([is-selected]) #done {
@@ -31,11 +32,11 @@
   }
 
   :host([is-selected]) #name {
-    color: rgb(26, 115, 232);
+    color: var(--google-blue-600);
   }
 
   #done {
-    color: rgb(26, 115, 232);
+    color: var(--google-blue-600);
     display: none;
     flex-shrink: 0;
     height: 17px;
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.html b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
index 508e1ce..2b712836 100644
--- a/chrome/browser/resources/nearby_share/nearby_discovery_page.html
+++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
@@ -6,8 +6,8 @@
   }
 
   #centerContent {
-    background: radial-gradient(160% 50% at 0 100%,
-        rgba(var(--google-blue-refresh-300-rgb), 0.25), rgba(255, 255, 255, 0));
+    background: linear-gradient(to top, var(--google-blue-50) 5%,
+                                rgba(255, 255, 255, 0) 50%);
     box-sizing: border-box;
     display: flex;
     flex-direction: column;
@@ -17,6 +17,7 @@
   }
 
   #deviceList {
+    align-self: stretch;
     display: block;
     margin-top: 12px;
     overflow: auto;
@@ -72,9 +73,10 @@
     flex-direction: row;
     font-size: 9px;
     line-height: 12px;
+    margin-block-end: 8px;
     margin-inline-end:  var(--nearby-page-space-inline);
     margin-inline-start: var(--nearby-page-space-inline);
-    padding: 8px;
+    padding: 0 8px;
   }
 
   #helpText {
@@ -96,9 +98,10 @@
   #process-row {
     align-items: flex-start;
     display: flex;
+    flex-grow: 1;
     justify-content: space-between;
-    margin: auto 0;
-    margin-block-start: 48px;
+    margin-block-end: 24px;
+    margin-block-start: 8px;
     overflow: hidden;
   }
 
@@ -110,7 +113,7 @@
 
   #placeholder {
     align-self: center;
-    color: rgb(95, 99, 104);
+    color: var(--google-grey-refresh-700);
     font-size: 13px;
     line-height: 20px;
     margin-inline-end: var(--nearby-page-space-large-inline);
@@ -125,6 +128,9 @@
         cancel-button-label="$i18n{nearbyShareActionsCancel}"
         cancel-button-event-name="close">
   <div id="centerContent" slot="content">
+    <cr-lottie animation-url="nearby_share_pulse_animation.json"
+        autoplay="true">
+    </cr-lottie>
     <div id="process-row">
       <nearby-preview send-preview="[[sendPreview]]"></nearby-preview>
       <div id="placeholder"
@@ -168,8 +174,5 @@
         </div>
       </div>
     </template>
-    <cr-lottie animation-url="nearby_share_pulse_animation.json"
-        autoplay="true">
-    </cr-lottie>
   </div>
 </nearby-page-template>
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_page_template.html b/chrome/browser/resources/nearby_share/shared/nearby_page_template.html
index 71c92f0..a878702 100644
--- a/chrome/browser/resources/nearby_share/shared/nearby_page_template.html
+++ b/chrome/browser/resources/nearby_share/shared/nearby_page_template.html
@@ -32,8 +32,14 @@
       }
 
       #pageTitle {
-        font-size: 1.1em;
+        font-size: 16px;
         font-weight: bold;
+        line-height: 24px;
+      }
+
+      #pageSubTitle {
+        font-size: 14px;
+        line-height: 20px;
       }
 
       #actions {
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
index 8e8bd1e..de4950e 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
@@ -75,7 +75,10 @@
     });
 
     this.updateChannelInfo_();
-    this.updateDeviceName_();
+
+    if (this.isHostnameSettingEnabled_) {
+      this.updateDeviceName_();
+    }
   },
 
   /**
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 1cab775..306e491 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -113,12 +113,13 @@
       "trigger_creator.h",
       "ui_manager.cc",
       "ui_manager.h",
+      "url_lookup_service_factory.cc",
+      "url_lookup_service_factory.h",
       "user_interaction_observer.cc",
       "user_interaction_observer.h",
     ]
     deps += [
       ":metrics_collector",
-      ":url_lookup_service_factory",
       ":verdict_cache_manager_factory",
       "//chrome/common/safe_browsing:proto",
       "//components/safe_browsing/content",
@@ -135,9 +136,12 @@
       "//components/safe_browsing/core/common:safe_browsing_prefs",
       "//components/safe_browsing/core/db:allowlist_checker_client",
       "//components/safe_browsing/core/db:metadata_proto",
+      "//components/safe_browsing/core/realtime:url_lookup_service",
       "//components/safe_browsing/core/triggers",
       "//components/safe_browsing/core/triggers:trigger_throttler",
+      "//components/signin/public/identity_manager",
       "//components/site_engagement/core/mojom:mojo_bindings",
+      "//content/public/browser",
       "//services/preferences/public/mojom:mojom",
     ]
     if (safe_browsing_mode == 1) {
@@ -146,6 +150,10 @@
       sources += [
         "../download/download_completion_blocker.cc",
         "../download/download_completion_blocker.h",
+        "chrome_enterprise_url_lookup_service.cc",
+        "chrome_enterprise_url_lookup_service.h",
+        "chrome_enterprise_url_lookup_service_factory.cc",
+        "chrome_enterprise_url_lookup_service_factory.h",
         "cloud_content_scanning/binary_fcm_service.cc",
         "cloud_content_scanning/binary_fcm_service.h",
         "cloud_content_scanning/binary_upload_service.cc",
@@ -245,25 +253,31 @@
           "incident_reporting/platform_state_store_win.cc",
         ]
       }
-      public_deps += [
-        ":chrome_enterprise_url_lookup_service",
-        ":chrome_enterprise_url_lookup_service_factory",
-      ]
       deps += [
         ":advanced_protection",
+        ":verdict_cache_manager_factory",
+        "//chrome/common",
         "//chrome/common/safe_browsing:archive_analyzer_results",
         "//chrome/common/safe_browsing:binary_feature_extractor",
         "//chrome/common/safe_browsing:download_type_util",
         "//chrome/services/file_util/public/cpp",
         "//components/content_settings/core/browser",
+        "//components/keyed_service/content",
         "//components/language/core/common",
         "//components/prefs",
         "//components/safe_browsing/core:client_model_proto",
+        "//components/safe_browsing/core:csd_proto",
+        "//components/safe_browsing/core:realtimeapi_proto",
+        "//components/safe_browsing/core:verdict_cache_manager",
         "//components/safe_browsing/core/db",
+        "//components/safe_browsing/core/realtime:policy_engine",
+        "//components/safe_browsing/core/realtime:url_lookup_service_base",
         "//components/security_interstitials/content:security_interstitial_page",
         "//components/security_interstitials/core:unsafe_resource",
+        "//components/sync",
         "//content/public/browser",
         "//net",
+        "//services/network/public/cpp:cpp",
       ]
       if (is_mac) {
         deps += [ "//chrome/common/safe_browsing:disk_image_type_sniffer_mac" ]
@@ -288,62 +302,6 @@
   }
 }
 
-if (safe_browsing_mode == 1) {
-  source_set("chrome_enterprise_url_lookup_service_factory") {
-    visibility = [ ":*" ]
-
-    sources = [
-      "chrome_enterprise_url_lookup_service_factory.cc",
-      "chrome_enterprise_url_lookup_service_factory.h",
-    ]
-
-    deps = [
-      ":chrome_enterprise_url_lookup_service",
-      ":verdict_cache_manager_factory",
-      "//chrome/common",
-      "//components/keyed_service/content",
-      "//content/public/browser",
-    ]
-  }
-
-  source_set("chrome_enterprise_url_lookup_service") {
-    visibility = [ ":*" ]
-
-    sources = [
-      "chrome_enterprise_url_lookup_service.cc",
-      "chrome_enterprise_url_lookup_service.h",
-    ]
-
-    deps = [
-      "//components/prefs",
-      "//components/safe_browsing/core:csd_proto",
-      "//components/safe_browsing/core:realtimeapi_proto",
-      "//components/safe_browsing/core:verdict_cache_manager",
-      "//components/safe_browsing/core/realtime:policy_engine",
-      "//components/safe_browsing/core/realtime:url_lookup_service_base",
-      "//components/sync",
-      "//services/network/public/cpp:cpp",
-    ]
-  }
-}
-
-source_set("url_lookup_service_factory") {
-  sources = [
-    "url_lookup_service_factory.cc",
-    "url_lookup_service_factory.h",
-  ]
-
-  deps = [
-    ":verdict_cache_manager_factory",
-    "//chrome/common",
-    "//components/keyed_service/content",
-    "//components/safe_browsing:buildflags",
-    "//components/safe_browsing/core/realtime:url_lookup_service",
-    "//components/signin/public/identity_manager",
-    "//content/public/browser",
-  ]
-}
-
 source_set("verdict_cache_manager_factory") {
   sources = [
     "verdict_cache_manager_factory.cc",
@@ -388,6 +346,7 @@
   ]
 
   deps = [
+    "//chrome/common",
     "//components/keyed_service/content",
     "//components/prefs",
     "//components/safe_browsing/core/common",
diff --git a/chrome/browser/safe_browsing/client_side_model_loader.cc b/chrome/browser/safe_browsing/client_side_model_loader.cc
index 9c78987..3dc6c77 100644
--- a/chrome/browser/safe_browsing/client_side_model_loader.cc
+++ b/chrome/browser/safe_browsing/client_side_model_loader.cc
@@ -23,6 +23,7 @@
 #include "base/time/time.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
@@ -63,10 +64,8 @@
     "https://ssl.gstatic.com/safebrowsing/csd/";
 const char ModelLoader::kClientModelNamePattern[] =
     "client_model_v5%s_variation_%d.pb";
+#if !BUILDFLAG(FULL_SAFE_BROWSING)
 const char ModelLoader::kClientModelFinchExperiment[] =
-#if BUILDFLAG(FULL_SAFE_BROWSING)
-    "ClientSideDetectionModel";
-#else
     "ClientSideDetectionModelOnAndroid";
 #endif
 const char ModelLoader::kClientModelFinchParam[] =
@@ -80,8 +79,13 @@
 
 // static
 int ModelLoader::GetModelNumber() {
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+  std::string num_str = base::GetFieldTrialParamValueByFeature(
+      kClientSideDetectionModelVersion, kClientModelFinchParam);
+#else
   std::string num_str = variations::GetVariationParamValue(
       kClientModelFinchExperiment, kClientModelFinchParam);
+#endif
   int model_number = 0;
   if (!base::StringToInt(num_str, &model_number)) {
     model_number = 6;  // Default model
diff --git a/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc b/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
index fa96c80..bfeb8be 100644
--- a/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
+++ b/chrome/browser/safe_browsing/client_side_model_loader_unittest.cc
@@ -20,6 +20,8 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "components/safe_browsing/buildflags.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/safe_browsing/core/proto/client_model.pb.h"
 #include "components/safe_browsing/core/proto/csd.pb.h"
 #include "components/variations/variations_associated_data.h"
@@ -76,6 +78,13 @@
   // Set up the finch experiment to control the model number
   // used in the model URL. This clears all existing state.
   void SetFinchModelNumber(int model_number) {
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        kClientSideDetectionModelVersion,
+        {{ ModelLoader::kClientModelFinchParam,
+           base::NumberToString(model_number) }});
+#else
     scoped_feature_list_.Reset();
     scoped_feature_list_.Init();
     variations::testing::ClearAllVariationIDs();
@@ -91,6 +100,7 @@
 
     ASSERT_TRUE(variations::AssociateVariationParams(
         ModelLoader::kClientModelFinchExperiment, group_name, params));
+#endif
   }
 
   // Set the URL for future SetModelFetchResponse() calls.
diff --git a/chrome/browser/serial/chrome_serial_browsertest.cc b/chrome/browser/serial/chrome_serial_browsertest.cc
index 7a3d348..56949b4 100644
--- a/chrome/browser/serial/chrome_serial_browsertest.cc
+++ b/chrome/browser/serial/chrome_serial_browsertest.cc
@@ -96,7 +96,7 @@
         removedPromise = new Promise(resolve => {
           navigator.serial.addEventListener(
               'disconnect', e => {
-                resolve(e.port === ports[0]);
+                resolve(e.target === ports[0]);
               }, { once: true });
         });
         return true;
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCoordinator.java
new file mode 100644
index 0000000..01925f7
--- /dev/null
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCoordinator.java
@@ -0,0 +1,48 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.share.long_screenshots;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+
+import org.chromium.chrome.browser.share.screenshot.ScreenshotCoordinator;
+import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+
+/**
+ * Handles the long screenshot action in the Sharing Hub and launches the screenshot editor.
+ */
+public class LongScreenshotsCoordinator extends ScreenshotCoordinator {
+    /**
+     * Constructs a new ScreenshotCoordinator which may launch the editor, or a fallback.
+     *
+     * @param activity The parent activity.
+     * @param tab The Tab which contains the content to share.
+     * @param chromeOptionShareCallback An interface to share sheet APIs.
+     * @param sheetController The {@link BottomSheetController} for the current activity.
+     * @param imageEditorModuleProvider An interface to install and/or instantiate the image editor.
+     */
+    public LongScreenshotsCoordinator(Activity activity, Tab tab,
+            ChromeOptionShareCallback chromeOptionShareCallback,
+            BottomSheetController sheetController,
+            ImageEditorModuleProvider imageEditorModuleProvider) {
+        super(activity, tab, chromeOptionShareCallback, sheetController, imageEditorModuleProvider);
+    }
+
+    /**
+     * Called after ShareSheetBottomSheetContent is closed. Calls the FDT service to
+     * generate a long screenshot, takes the user through the cropping flow, then
+     * launches the bottom bar.
+     */
+    @Override
+    public void captureScreenshot() {
+        // TODO(crbug/1142520): Use service to capture from FDT, crop the image, assign to
+        // mScreenshot
+        mScreenshot = Bitmap.createBitmap(512, 1024, Bitmap.Config.ARGB_8888);
+        launchSharesheet();
+    }
+}
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
index 2d4fd32..9bcd2da 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/screenshot/ScreenshotCoordinator.java
@@ -34,15 +34,15 @@
     private final ImageEditorModuleProvider mImageEditorModuleProvider;
 
     private EditorScreenshotSource mScreenshotSource;
-    private Bitmap mScreenshot;
+    protected Bitmap mScreenshot;
 
     /**
      * Constructs a new ScreenshotCoordinator which may launch the editor, or a fallback.
      *
      * @param activity The parent activity.
      * @param tab The Tab which contains the content to share.
-     * @param screenshotSource The Source interface to use to take a screenshot.
      * @param chromeOptionShareCallback An interface to share sheet APIs.
+     * @param sheetController The {@link BottomSheetController} for the current activity.
      * @param imageEditorModuleProvider An interface to install and/or instantiate the image editor.
      */
     public ScreenshotCoordinator(Activity activity, Tab tab,
@@ -127,7 +127,7 @@
     /**
      * Opens the screenshot sharesheet.
      */
-    private void launchSharesheet() {
+    protected void launchSharesheet() {
         ScreenshotShareSheetDialogCoordinator shareSheet =
                 new ScreenshotShareSheetDialogCoordinator(mActivity, mDialog, mScreenshot, mTab,
                         mChromeOptionShareCallback, this::retryInstallEditor);
@@ -144,6 +144,10 @@
             // If the module does not exist, nothing to do.
             return;
         }
+        if (mImageEditorModuleProvider.isModuleInstalled()) {
+            launchEditor();
+            return;
+        }
         installEditor(false, onSuccess);
     }
 
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index b60fa3f..8211b22 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -24,6 +24,7 @@
 import org.chromium.chrome.browser.settings.SettingsLauncher;
 import org.chromium.chrome.browser.share.ChromeShareExtras;
 import org.chromium.chrome.browser.share.link_to_text.LinkToTextCoordinator;
+import org.chromium.chrome.browser.share.long_screenshots.LongScreenshotsCoordinator;
 import org.chromium.chrome.browser.share.qrcode.QrCodeCoordinator;
 import org.chromium.chrome.browser.share.screenshot.ScreenshotCoordinator;
 import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfCoordinator;
@@ -216,6 +217,9 @@
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_SCREENSHOT)) {
             mOrderedFirstPartyOptions.add(createScreenshotFirstPartyOption());
         }
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_LONG_SCREENSHOT)) {
+            mOrderedFirstPartyOptions.add(createLongScreenshotsFirstPartyOption());
+        }
         mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARING_HUB_V15)) {
             mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption());
@@ -273,6 +277,27 @@
                 /*disableForMultiWindow=*/true);
     }
 
+    private FirstPartyOption createLongScreenshotsFirstPartyOption() {
+        PropertyModel propertyModel = ShareSheetPropertyModelBuilder.createPropertyModel(
+                AppCompatResources.getDrawable(mActivity, R.drawable.long_screenshot),
+                mActivity.getResources().getString(R.string.sharing_long_screenshot), (view) -> {
+                    RecordUserAction.record("SharingHubAndroid.LongScreenshotSelected");
+                    recordTimeToShare(mShareStartTime);
+                    mScreenshotCoordinator = new LongScreenshotsCoordinator(mActivity,
+                            mTabProvider.get(), mChromeOptionShareCallback, mBottomSheetController,
+                            mImageEditorModuleProvider);
+                    // Capture a screenshot once the bottom sheet is fully hidden. The
+                    // observer will then remove itself.
+                    mBottomSheetController.addObserver(mSheetObserver);
+                    mBottomSheetController.hideContent(mBottomSheetContent, true);
+                });
+        return new FirstPartyOption(propertyModel,
+                Arrays.asList(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
+                        ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE),
+                /*contentTypesToDisableFor=*/Collections.emptySet(),
+                /*disableForMultiWindow=*/true);
+    }
+
     private FirstPartyOption createCopyLinkFirstPartyOption() {
         return new FirstPartyOptionBuilder(
                 ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE)
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
index 73d88cf..efc94b7 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
@@ -26,6 +26,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -100,8 +101,9 @@
         createFirstPartyRecyclerViews(firstPartyModels);
 
         RecyclerView thirdParty = this.getContentView().findViewById(R.id.share_sheet_other_apps);
-        populateView(
-                thirdPartyModels, this.getContentView().findViewById(R.id.share_sheet_other_apps));
+        populateView(thirdPartyModels,
+                this.getContentView().findViewById(R.id.share_sheet_other_apps),
+                /*firstParty=*/false);
         thirdParty.addOnScrollListener(
                 new ScrollEventReporter("SharingHubAndroid.ThirdPartyAppsScrolled"));
     }
@@ -113,20 +115,21 @@
             View divider = this.getContentView().findViewById(R.id.share_sheet_divider);
             divider.setVisibility(View.VISIBLE);
             firstPartyRow.setVisibility(View.VISIBLE);
-            populateView(firstPartyModels, firstPartyRow);
+            populateView(firstPartyModels, firstPartyRow, /*firstParty=*/true);
             firstPartyRow.addOnScrollListener(
                     new ScrollEventReporter("SharingHubAndroid.FirstPartyAppsScrolled"));
         }
     }
 
-    private void populateView(List<PropertyModel> models, RecyclerView view) {
+    private void populateView(List<PropertyModel> models, RecyclerView view, boolean firstParty) {
         ModelList modelList = new ModelList();
         for (PropertyModel model : models) {
             modelList.add(new ListItem(SHARE_SHEET_ITEM, model));
         }
         SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(modelList);
         adapter.registerType(SHARE_SHEET_ITEM, new LayoutViewBuilder(R.layout.share_sheet_item),
-                ShareSheetBottomSheetContent::bindShareItem);
+                (firstParty ? ShareSheetBottomSheetContent::bindShareItem
+                            : ShareSheetBottomSheetContent::bind3PShareItem));
         view.setAdapter(adapter);
         LinearLayoutManager layoutManager =
                 new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false);
@@ -146,6 +149,26 @@
         }
     }
 
+    private static void bind3PShareItem(
+            PropertyModel model, ViewGroup parent, PropertyKey propertyKey) {
+        bindShareItem(model, parent, propertyKey);
+        if (ShareSheetItemViewProperties.ICON.equals(propertyKey)) {
+            ImageView view = (ImageView) parent.findViewById(R.id.icon);
+
+            final int iconSize =
+                    ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
+                            R.dimen.sharing_hub_3p_icon_size);
+            final int paddingTop =
+                    ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
+                            R.dimen.sharing_hub_3p_icon_padding_top);
+            ViewGroup.LayoutParams params = view.getLayoutParams();
+            params.height = iconSize;
+            params.width = iconSize;
+            view.requestLayout();
+            parent.setPadding(0, paddingTop, 0, 0);
+        }
+    }
+
     private void createPreview(Set<Integer> contentTypes, String fileContentType) {
         // Default preview is to show title + url.
         String title = mParams.getTitle();
diff --git a/chrome/browser/share/android/java_sources.gni b/chrome/browser/share/android/java_sources.gni
index 331104c..4fa0e11 100644
--- a/chrome/browser/share/android/java_sources.gni
+++ b/chrome/browser/share/android/java_sources.gni
@@ -9,6 +9,7 @@
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/clipboard/ClipboardImageFileProvider.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextCoordinator.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextMetricsBridge.java",
+  "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/LongScreenshotsCoordinator.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerationRequest.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java",
diff --git a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.cc b/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.cc
deleted file mode 100644
index 67478929..0000000
--- a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h"
-
-#include "base/metrics/histogram_macros.h"
-#include "base/rand_util.h"
-#include "third_party/blink/public/common/features.h"
-
-HttpsImageCompressionBypassDecider::HttpsImageCompressionBypassDecider() =
-    default;
-
-HttpsImageCompressionBypassDecider::~HttpsImageCompressionBypassDecider() =
-    default;
-
-bool HttpsImageCompressionBypassDecider::ShouldBypassNow() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
-  bool should_bypass =
-      bypassed_until_time_ && base::TimeTicks::Now() <= bypassed_until_time_;
-  UMA_HISTOGRAM_BOOLEAN("SubresourceRedirect.PageLoad.BypassResult",
-                        should_bypass);
-  return should_bypass;
-}
-
-void HttpsImageCompressionBypassDecider::NotifyCompressedImageFetchFailed(
-    base::TimeDelta retry_after) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
-  if (bypassed_until_time_)
-    return;  // Bypass is already enabled due to a previous failure.
-
-  if (!retry_after.is_zero()) {
-    // Choose the time mentioned in retry_after, but cap it to 5 minutes.
-    retry_after = std::min(retry_after, base::TimeDelta::FromMinutes(5));
-  } else {
-    // Bypass for a random duration between 1 to 5 minutes.
-    retry_after = base::TimeDelta::FromSeconds(base::RandInt(1 * 60, 5 * 60));
-  }
-  bypassed_until_time_ = base::TimeTicks::Now() + retry_after;
-  UMA_HISTOGRAM_LONG_TIMES("SubresourceRedirect.BypassDuration", retry_after);
-}
diff --git a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h b/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h
deleted file mode 100644
index d1af5d8..0000000
--- a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SUBRESOURCE_REDIRECT_HTTPS_IMAGE_COMPRESSION_BYPASS_DECIDER_H_
-#define CHROME_BROWSER_SUBRESOURCE_REDIRECT_HTTPS_IMAGE_COMPRESSION_BYPASS_DECIDER_H_
-
-#include "base/optional.h"
-#include "base/sequence_checker.h"
-#include "base/time/time.h"
-
-// Interface to decide whether https image compression should be bypassed for a
-// page load. Whenever an image fetch for compression server fails, image
-// compression feature is turned off for a random 1-5 minute duration or until
-// the time mentioned in Retry-After response header from compression server.
-class HttpsImageCompressionBypassDecider {
- public:
-  HttpsImageCompressionBypassDecider();
-  ~HttpsImageCompressionBypassDecider();
-
-  // Returns whether https image compression should be bypassed for the current
-  // page load. Should be called only once per page load, since it records
-  // histograms that is expected once per page load.
-  bool ShouldBypassNow();
-
-  // Notifies the decider that an image compression fetch had failed, which will
-  // start bypassing image compression feature for subsequent pageloads.
-  void NotifyCompressedImageFetchFailed(base::TimeDelta retry_after);
-
-  base::Optional<base::TimeTicks> GetBypassUntilTimeForTesting() const {
-    return bypassed_until_time_;
-  }
-  void SetBypassUntilTimeForTesting(base::TimeTicks bypass_until) {
-    bypassed_until_time_ = bypass_until;
-  }
-
- private:
-  // The time until which image compression should be bypassed. Null time
-  // indicates no bypass.
-  base::Optional<base::TimeTicks> bypassed_until_time_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-#endif  // CHROME_BROWSER_SUBRESOURCE_REDIRECT_HTTPS_IMAGE_COMPRESSION_BYPASS_DECIDER_H_
diff --git a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider_unittest.cc b/chrome/browser/subresource_redirect/https_image_compression_bypass_decider_unittest.cc
deleted file mode 100644
index df59304..0000000
--- a/chrome/browser/subresource_redirect/https_image_compression_bypass_decider_unittest.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h"
-
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/task_environment.h"
-#include "base/time/time.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/features.h"
-
-class HttpsImageCompressionBypassDeciderTest : public testing::Test {
- public:
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kSubresourceRedirect);
-  }
-
- protected:
-  base::test::SingleThreadTaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  base::test::ScopedFeatureList scoped_feature_list_;
-  base::HistogramTester histogram_tester_;
-  HttpsImageCompressionBypassDecider https_image_compression_bypass_decider_;
-};
-
-TEST_F(HttpsImageCompressionBypassDeciderTest, TestNoBypassOnInit) {
-  EXPECT_FALSE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
-}
-
-// When an image fetch fails, it should be bypassed for a random duration.
-TEST_F(HttpsImageCompressionBypassDeciderTest, TestRandomBypass) {
-  https_image_compression_bypass_decider_.NotifyCompressedImageFetchFailed(
-      base::TimeDelta());
-  histogram_tester_.ExpectTotalCount("SubresourceRedirect.BypassDuration", 1);
-  EXPECT_TRUE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 1);
-
-  // Image compression is bypassed until a minimum of one minute.
-  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(59));
-  EXPECT_TRUE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 2);
-
-  // After another 5 minutes, bypass should get disabled.
-  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5));
-  EXPECT_FALSE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
-}
-
-TEST_F(HttpsImageCompressionBypassDeciderTest, TestExactBypass) {
-  // Bypass for 30 seconds
-  https_image_compression_bypass_decider_.NotifyCompressedImageFetchFailed(
-      base::TimeDelta::FromSeconds(30));
-  histogram_tester_.ExpectUniqueSample("SubresourceRedirect.BypassDuration",
-                                       30000, 1);
-  EXPECT_TRUE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 1);
-
-  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(31));
-  EXPECT_FALSE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
-}
-
-TEST_F(HttpsImageCompressionBypassDeciderTest, TestInvalidBypassDuration) {
-  // Bypass for too long duration will limit the bypass to only 5 minutes.
-  https_image_compression_bypass_decider_.NotifyCompressedImageFetchFailed(
-      base::TimeDelta::FromMinutes(6));
-  histogram_tester_.ExpectUniqueSample("SubresourceRedirect.BypassDuration",
-                                       5 * 60 * 1000, 1);
-  EXPECT_TRUE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectUniqueSample(
-      "SubresourceRedirect.PageLoad.BypassResult", true, 1);
-
-  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(6));
-  EXPECT_FALSE(https_image_compression_bypass_decider_.ShouldBypassNow());
-  histogram_tester_.ExpectBucketCount(
-      "SubresourceRedirect.PageLoad.BypassResult", false, 1);
-}
diff --git a/chrome/browser/subresource_redirect/litepages_service_bypass_decider.cc b/chrome/browser/subresource_redirect/litepages_service_bypass_decider.cc
new file mode 100644
index 0000000..4892272
--- /dev/null
+++ b/chrome/browser/subresource_redirect/litepages_service_bypass_decider.cc
@@ -0,0 +1,56 @@
+// 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/subresource_redirect/litepages_service_bypass_decider.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/subresource_redirect/subresource_redirect_util.h"
+#include "net/http/http_status_code.h"
+#include "third_party/blink/public/common/features.h"
+
+LitePagesServiceBypassDecider::LitePagesServiceBypassDecider() = default;
+
+LitePagesServiceBypassDecider::~LitePagesServiceBypassDecider() = default;
+
+bool LitePagesServiceBypassDecider::ShouldAllowNow() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
+  bool should_allow =
+      !bypassed_until_time_ || base::TimeTicks::Now() > bypassed_until_time_;
+  base::UmaHistogramBoolean("SubresourceRedirect.LitePagesService.BypassResult",
+                            !should_allow);
+  return should_allow;
+}
+
+void LitePagesServiceBypassDecider::NotifyFetchFailureWithResponseCode(
+    int response_code,
+    base::TimeDelta retry_after) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
+  if (response_code == net::HTTP_SERVICE_UNAVAILABLE ||
+      response_code == net::HTTP_FORBIDDEN) {
+    NotifyFetchFailure(retry_after);
+  }
+}
+
+void LitePagesServiceBypassDecider::NotifyFetchFailure(
+    base::TimeDelta retry_after) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(base::FeatureList::IsEnabled(blink::features::kSubresourceRedirect));
+  if (!retry_after.is_zero()) {
+    // Choose the time mentioned in retry_after, but cap it to a max value.
+    retry_after = std::min(
+        retry_after, subresource_redirect::GetLitePagesBypassMaxDuration());
+  } else {
+    // Bypass for a random duration.
+    retry_after = subresource_redirect::GetLitePagesBypassRandomDuration();
+  }
+  // Take the maximum possible bypass duration.
+  bypassed_until_time_ = bypassed_until_time_
+                             ? std::max(*bypassed_until_time_,
+                                        base::TimeTicks::Now() + retry_after)
+                             : base::TimeTicks::Now() + retry_after;
+  base::UmaHistogramLongTimes("SubresourceRedirect.BypassDuration",
+                              retry_after);
+}
diff --git a/chrome/browser/subresource_redirect/litepages_service_bypass_decider.h b/chrome/browser/subresource_redirect/litepages_service_bypass_decider.h
new file mode 100644
index 0000000..ff36a30
--- /dev/null
+++ b/chrome/browser/subresource_redirect/litepages_service_bypass_decider.h
@@ -0,0 +1,52 @@
+// 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_SUBRESOURCE_REDIRECT_LITEPAGES_SERVICE_BYPASS_DECIDER_H_
+#define CHROME_BROWSER_SUBRESOURCE_REDIRECT_LITEPAGES_SERVICE_BYPASS_DECIDER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+
+// Interface to decide whether LitePages service should be bypassed, which is
+// used for fetching compressed image and fetching robots.txt. Whenever an
+// LitePages server fetch fails, subsequent fetches to LitePages is turned off
+// for a random 1-5 minute duration or until the time mentioned in Retry-After
+// response header from the server or up to a maximum duration specified by
+// experimental params.
+class LitePagesServiceBypassDecider
+    : public base::SupportsWeakPtr<LitePagesServiceBypassDecider> {
+ public:
+  LitePagesServiceBypassDecider();
+  ~LitePagesServiceBypassDecider();
+
+  // Returns whether a fetch to LitePages service should be allowed.
+  bool ShouldAllowNow();
+
+  // Notifies the decider that a LitePages fetch had failed, with the
+  // |response_code. This will start bypassing subsequent LitePage fetches.
+  void NotifyFetchFailureWithResponseCode(int response_code,
+                                          base::TimeDelta retry_after);
+
+  // Notifies the decider that a LitePages fetch had failed, which will
+  // start bypassing subsequent LitePage fetches.
+  void NotifyFetchFailure(base::TimeDelta retry_after);
+
+  base::Optional<base::TimeTicks> GetBypassUntilTimeForTesting() const {
+    return bypassed_until_time_;
+  }
+  void SetBypassUntilTimeForTesting(base::TimeTicks bypass_until) {
+    bypassed_until_time_ = bypass_until;
+  }
+
+ private:
+  // The time until which image compression should be bypassed. Null time
+  // indicates no bypass.
+  base::Optional<base::TimeTicks> bypassed_until_time_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+#endif  // CHROME_BROWSER_SUBRESOURCE_REDIRECT_LITEPAGES_SERVICE_BYPASS_DECIDER_H_
diff --git a/chrome/browser/subresource_redirect/litepages_service_bypass_decider_unittest.cc b/chrome/browser/subresource_redirect/litepages_service_bypass_decider_unittest.cc
new file mode 100644
index 0000000..0a6ef03
--- /dev/null
+++ b/chrome/browser/subresource_redirect/litepages_service_bypass_decider_unittest.cc
@@ -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.
+
+#include "chrome/browser/subresource_redirect/litepages_service_bypass_decider.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+
+class LitePagesServiceBypassDeciderTest : public testing::Test {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        blink::features::kSubresourceRedirect);
+  }
+
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::test::ScopedFeatureList scoped_feature_list_;
+  base::HistogramTester histogram_tester_;
+  LitePagesServiceBypassDecider litepages_service_bypass_decider_;
+};
+
+TEST_F(LitePagesServiceBypassDeciderTest, TestNoBypassOnInit) {
+  EXPECT_TRUE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
+}
+
+// When a LitePages fetch fails, it should be bypassed for a random duration.
+TEST_F(LitePagesServiceBypassDeciderTest, TestRandomBypass) {
+  litepages_service_bypass_decider_.NotifyFetchFailure(base::TimeDelta());
+  histogram_tester_.ExpectTotalCount("SubresourceRedirect.BypassDuration", 1);
+  EXPECT_FALSE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 1);
+
+  // Subsequent fetches are bypassed until a minimum of one minute.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(59));
+  EXPECT_FALSE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 2);
+
+  // After another 5 minutes, bypass should get disabled.
+  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5));
+  EXPECT_TRUE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
+}
+
+TEST_F(LitePagesServiceBypassDeciderTest, TestExactBypass) {
+  // Bypass for 30 seconds
+  litepages_service_bypass_decider_.NotifyFetchFailure(
+      base::TimeDelta::FromSeconds(30));
+  histogram_tester_.ExpectUniqueSample("SubresourceRedirect.BypassDuration",
+                                       30000, 1);
+  EXPECT_FALSE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 1);
+
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(31));
+  EXPECT_TRUE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
+}
+
+TEST_F(LitePagesServiceBypassDeciderTest, TestInvalidBypassDuration) {
+  // Bypass for too long duration will limit the bypass to only 5 minutes.
+  litepages_service_bypass_decider_.NotifyFetchFailure(
+      base::TimeDelta::FromMinutes(6));
+  histogram_tester_.ExpectUniqueSample("SubresourceRedirect.BypassDuration",
+                                       5 * 60 * 1000, 1);
+  EXPECT_FALSE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectUniqueSample(
+      "SubresourceRedirect.LitePagesService.BypassResult", true, 1);
+
+  task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(6));
+  EXPECT_TRUE(litepages_service_bypass_decider_.ShouldAllowNow());
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LitePagesService.BypassResult", false, 1);
+}
diff --git a/chrome/browser/subresource_redirect/origin_robots_rules_unittest.cc b/chrome/browser/subresource_redirect/origin_robots_rules_unittest.cc
index 96fc29e..6cf7c76 100644
--- a/chrome/browser/subresource_redirect/origin_robots_rules_unittest.cc
+++ b/chrome/browser/subresource_redirect/origin_robots_rules_unittest.cc
@@ -93,7 +93,8 @@
                         net::HttpStatusCode http_status = net::HTTP_OK,
                         bool is_cache_hit = false,
                         const std::string& retry_after = "") {
-    GURL url(lite_pages_url + net::EscapeQueryParamValue(robots_origin, true));
+    GURL url(lite_pages_url +
+             net::EscapeQueryParamValue(robots_origin + "robots.txt", true));
     network::mojom::URLResponseHeadPtr head =
         network::CreateURLResponseHead(http_status);
     head->was_fetched_via_cache = is_cache_hit;
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_util.cc b/chrome/browser/subresource_redirect/subresource_redirect_util.cc
index ac53f12..2edec12 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_util.cc
+++ b/chrome/browser/subresource_redirect/subresource_redirect_util.cc
@@ -4,11 +4,12 @@
 
 #include "chrome/browser/subresource_redirect/subresource_redirect_util.h"
 
+#include "base/rand_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/subresource_redirect/https_image_compression_bypass_decider.h"
 #include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
+#include "chrome/browser/subresource_redirect/litepages_service_bypass_decider.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/escape.h"
@@ -95,8 +96,8 @@
     return false;
   }
 
-  if (data_reduction_proxy_settings->https_image_compression_bypass_decider()
-          ->ShouldBypassNow()) {
+  if (!data_reduction_proxy_settings->litepages_service_bypass_decider()
+           ->ShouldAllowNow()) {
     return false;
   }
 
@@ -118,14 +119,19 @@
 void NotifyCompressedImageFetchFailed(content::WebContents* web_contents,
                                       base::TimeDelta retry_after) {
   GetDataReductionProxyChromeSettings(web_contents)
-      ->https_image_compression_bypass_decider()
-      ->NotifyCompressedImageFetchFailed(retry_after);
+      ->litepages_service_bypass_decider()
+      ->NotifyFetchFailure(retry_after);
 }
 
 GURL GetRobotsServerURL(const url::SchemeHostPort& origin) {
   DCHECK(ShouldEnableLoginRobotsCheckedCompression());
   DCHECK(origin.IsValid());
 
+  GURL origin_url = origin.GetURL();
+  GURL::Replacements origin_replacement;
+  origin_replacement.SetPathStr("/robots.txt");
+  origin_url = origin_url.ReplaceComponents(origin_replacement);
+
   auto lite_page_robots_origin = base::GetFieldTrialParamValueByFeature(
       blink::features::kSubresourceRedirect, "lite_page_robots_origin");
   GURL lite_page_robots_url(lite_page_robots_origin.empty()
@@ -133,8 +139,7 @@
                                 : lite_page_robots_origin);
 
   std::string query_str =
-      "u=" +
-      net::EscapeQueryParamValue(origin.GetURL().spec(), true /* use_plus */);
+      "u=" + net::EscapeQueryParamValue(origin_url.spec(), true /* use_plus */);
 
   GURL::Replacements replacements;
   replacements.SetPathStr("/robots");
@@ -145,4 +150,21 @@
   return lite_page_robots_url;
 }
 
+base::TimeDelta GetLitePagesBypassRandomDuration() {
+  // Default is a random duration between 1 to 5 minutes.
+  return base::TimeDelta::FromSeconds(
+      base::RandInt(base::GetFieldTrialParamByFeatureAsInt(
+                        blink::features::kSubresourceRedirect,
+                        "litepages_bypass_random_duration_min_secs", 60),
+                    base::GetFieldTrialParamByFeatureAsInt(
+                        blink::features::kSubresourceRedirect,
+                        "litepages_bypass_random_duration_max_secs", 300)));
+}
+
+base::TimeDelta GetLitePagesBypassMaxDuration() {
+  return base::TimeDelta::FromSeconds(base::GetFieldTrialParamByFeatureAsInt(
+      blink::features::kSubresourceRedirect,
+      "litepages_bypass_max_duration_secs", 300));
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_util.h b/chrome/browser/subresource_redirect/subresource_redirect_util.h
index 4ba3167..1cb166f 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_util.h
+++ b/chrome/browser/subresource_redirect/subresource_redirect_util.h
@@ -48,6 +48,13 @@
 // |origin|.
 GURL GetRobotsServerURL(const url::SchemeHostPort& origin);
 
+// Returns a random duration LitePages service should bypass for, when a
+// LitePages response fails without RetryAfter header.
+base::TimeDelta GetLitePagesBypassRandomDuration();
+
+// Returns the maximum duration LitePages service should be bypassed.
+base::TimeDelta GetLitePagesBypassMaxDuration();
+
 }  // namespace subresource_redirect
 
 #endif  // CHROME_BROWSER_SUBRESOURCE_REDIRECT_SUBRESOURCE_REDIRECT_UTIL_H_
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_util_unit_test.cc b/chrome/browser/subresource_redirect/subresource_redirect_util_unit_test.cc
index 8aed44a..d84c2dcd 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_util_unit_test.cc
+++ b/chrome/browser/subresource_redirect/subresource_redirect_util_unit_test.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
@@ -32,9 +33,10 @@
 
   for (auto* origin :
        {"https://foo.com/", "https://www.foo.com/", "http://foo.com/"}) {
-    EXPECT_EQ(GURL("https://litepages.googlezip.net/robots?u=" +
-                   EscapeURLForQueryParam(origin)),
-              GetRobotsServerURL(url::SchemeHostPort(GURL(origin))));
+    EXPECT_EQ(
+        GURL("https://litepages.googlezip.net/robots?u=" +
+             EscapeURLForQueryParam(base::StrCat({origin, "robots.txt"}))),
+        GetRobotsServerURL(url::SchemeHostPort(GURL(origin))));
   }
 }
 
@@ -49,9 +51,10 @@
 
   for (auto* origin :
        {"https://foo.com/", "https://www.foo.com/", "http://foo.com/"}) {
-    EXPECT_EQ(GURL("https://modified.litepages.com/robots?u=" +
-                   EscapeURLForQueryParam(origin)),
-              GetRobotsServerURL(url::SchemeHostPort(GURL(origin))));
+    EXPECT_EQ(
+        GURL("https://modified.litepages.com/robots?u=" +
+             EscapeURLForQueryParam(base::StrCat({origin, "robots.txt"}))),
+        GetRobotsServerURL(url::SchemeHostPort(GURL(origin))));
   }
 }
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 040b7b6..c5fa355 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -4133,6 +4133,10 @@
         Screenshot
       </message>
 
+      <message name="IDS_SHARING_LONG_SCREENSHOT" desc="Label for Long Screenshot button in the sharing hub.">
+        Long Screenshot
+      </message>
+
       <message name="IDS_SHARING_HIGHLIGHTS" desc="Label for Link-to-text button in the sharing hub.">
         Link to text
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_LONG_SCREENSHOT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_LONG_SCREENSHOT.png.sha1
new file mode 100644
index 0000000..0b91990
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_LONG_SCREENSHOT.png.sha1
@@ -0,0 +1 @@
+bde0be0e4db2cd588629c99133e37d79430b440d
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
index 37c8cf9..f299b72 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
@@ -159,3 +159,8 @@
 void AccessibilityControllerClient::OnSwitchAccessDisabled() {
   chromeos::AccessibilityManager::Get()->OnSwitchAccessDisabled();
 }
+
+void AccessibilityControllerClient::OnSelectToSpeakPanelAction(
+    ash::SelectToSpeakPanelAction action) {
+  chromeos::AccessibilityManager::Get()->OnSelectToSpeakPanelAction(action);
+}
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
index 5a167465..a9628e4 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.h
@@ -34,6 +34,8 @@
       gfx::Point& point_in_screen) override;
   void MagnifierBoundsChanged(const gfx::Rect& bounds_in_screen) override;
   void OnSwitchAccessDisabled() override;
+  void OnSelectToSpeakPanelAction(
+      ash::SelectToSpeakPanelAction action) override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AccessibilityControllerClient);
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
index e7dec8d..d04a6ae 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
@@ -52,6 +52,10 @@
   void RequestSelectToSpeakStateChange() override {
     ++select_to_speak_state_changes_;
   }
+  void OnSelectToSpeakPanelAction(
+      ash::SelectToSpeakPanelAction action) override {
+    last_select_to_speak_panel_action_ = action;
+  }
 
   ash::AccessibilityAlert last_a11y_alert_ = ash::AccessibilityAlert::NONE;
   int32_t last_sound_key_ = -1;
@@ -63,6 +67,8 @@
   int on_two_finger_touch_stop_count_ = 0;
   int spoken_feedback_toggle_count_down_ = -1;
   int select_to_speak_state_changes_ = 0;
+  ash::SelectToSpeakPanelAction last_select_to_speak_panel_action_ =
+      ash::SelectToSpeakPanelAction::kNone;
 
  private:
   bool dictation_on_ = false;
@@ -139,4 +145,10 @@
   // Tests RequestSelectToSpeakStateChange method call.
   client.RequestSelectToSpeakStateChange();
   EXPECT_EQ(1, client.select_to_speak_state_changes_);
+
+  // Tests OnSelectToSpeakPanelAction method call.
+  const ash::SelectToSpeakPanelAction action =
+      ash::SelectToSpeakPanelAction::kPause;
+  client.OnSelectToSpeakPanelAction(action);
+  EXPECT_EQ(action, client.last_select_to_speak_panel_action_);
 }
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
index 0397670..5be6761 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
@@ -42,6 +42,9 @@
 
 void FakeAccessibilityController::HideSelectToSpeakPanel() {}
 
+void FakeAccessibilityController::OnSelectToSpeakPanelAction(
+    ash::SelectToSpeakPanelAction action) {}
+
 void FakeAccessibilityController::HideSwitchAccessBackButton() {}
 
 void FakeAccessibilityController::HideSwitchAccessMenu() {}
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
index 894e8ecf0..12993d6d 100644
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
+++ b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
@@ -30,6 +30,8 @@
       ash::SelectToSpeakEventHandlerDelegate* delegate) override;
   void ShowSelectToSpeakPanel(const gfx::Rect& anchor, bool is_paused) override;
   void HideSelectToSpeakPanel() override;
+  void OnSelectToSpeakPanelAction(
+      ash::SelectToSpeakPanelAction action) override;
   void HideSwitchAccessBackButton() override;
   void HideSwitchAccessMenu() override;
   void ShowSwitchAccessBackButton(const gfx::Rect& anchor) override;
diff --git a/chrome/browser/ui/webui/discards/graph_dump_impl.h b/chrome/browser/ui/webui/discards/graph_dump_impl.h
index 5ae0874..4257f12 100644
--- a/chrome/browser/ui/webui/discards/graph_dump_impl.h
+++ b/chrome/browser/ui/webui/discards/graph_dump_impl.h
@@ -101,10 +101,13 @@
   void OnFirstContentfulPaint(
       const performance_manager::FrameNode* frame_node,
       base::TimeDelta time_since_navigation_start) override {}
+  // Ignored.
   void OnViewportIntersectionChanged(
       const performance_manager::FrameNode* frame_node) override {}
+  // Ignored.
   void OnFrameVisibilityChanged(
-      const performance_manager::FrameNode* frame_node) override {}
+      const performance_manager::FrameNode* frame_node,
+      performance_manager::FrameNode::Visibility previous_value) override {}
 
   // PageNodeObserver implementation:
   void OnPageNodeAdded(const performance_manager::PageNode* page_node) override;
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index ecde223a..b92205a 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1605873573-9397db153d32d8ff4c4f46949924e1cd67fa6977.profdata
+chrome-linux-master-1605895128-ea3f86e674d4361d5284b25de8910e19181b674b.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 9814992..c1e70c2 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1605268075-3755a079b65652d38832c53840c8030bab959806.profdata
+chrome-mac-master-1605895128-00d55e6bb356ebbc13be21f27095ad3409f51b0e.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 1c1e05b..8e1ce21 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1605754194-4e76916f5340c97913d651fe425b3570e2df369d.profdata
+chrome-win32-master-1605873573-36e181f5df5da0c9aa7b43458275355d6ec5a576.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 7e1ce27..a222678 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1605862616-5037a5c6c628a5354743fcf375cbc392b8b36b5b.profdata
+chrome-win64-master-1605884021-38c8ae5c2e6706d9e606dfe2bcddc4d1100361a4.profdata
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 9ce5fca..95949634 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -203,6 +203,12 @@
         "type": "string",
         "enum": [ "selectToSpeakNavigationControl" ],
         "description": "Subset of accessibility features."
+      },
+      {
+        "id": "SelectToSpeakPanelAction",
+        "type": "string",
+        "enum": [ "previousParagraph", "previousSentence", "pause", "resume", "nextSentence", "nextParagraph", "exit" ],
+        "description": "Actions that can be performed in the Select-to-speak panel."
       }
     ],
     "functions": [
@@ -595,6 +601,18 @@
         "platforms": ["chromeos"]
       },
       {
+        "name": "onSelectToSpeakPanelAction",
+        "type": "function",
+        "description": "Fired when an action is performed in the Select-to-speak panel.",
+        "parameters": [
+          {
+            "name": "action",
+            "$ref": "SelectToSpeakPanelAction"
+          }
+        ],
+        "platforms": ["chromeos"]
+      },
+      {
         "name": "onSwitchAccessCommand",
         "type": "function",
         "description": "Fired when Chrome OS has received a key event corresponding to a Switch Access command.",
diff --git a/chrome/common/extensions/api/scripting.idl b/chrome/common/extensions/api/scripting.idl
index 1b2d7e9..4da3b22 100644
--- a/chrome/common/extensions/api/scripting.idl
+++ b/chrome/common/extensions/api/scripting.idl
@@ -33,9 +33,17 @@
     // A JavaScript function to inject. This function will be serialized, and
     // then deserialized for injection. This means that any bound parameters
     // and execution context will be lost.
-    // At least one of "file" and "function" must be specified.
+    // Exactly one of <code>files</code> and <code>function</code> must be
+    // specified.
     [serializableFunction]InjectedFunction? function;
 
+    // The path of the JS files to inject, relative to the extension's root
+    // directory.
+    // NOTE: Currently a maximum of one file is supported.
+    // Exactly one of <code>files</code> and <code>function</code> must be
+    // specified.
+    DOMString[]? files;
+
     // Details specifying the target into which to inject the script.
     InjectionTarget target;
   };
@@ -45,8 +53,17 @@
     InjectionTarget target;
 
     // A string containing the CSS to inject.
+    // Exactly one of <code>files</code> and <code>css</code> must be
+    // specified.
     DOMString? css;
 
+    // The path of the CSS files to inject, relative to the extension's root
+    // directory.
+    // NOTE: Currently a maximum of one file is supported.
+    // Exactly one of <code>files</code> and <code>css</code> must be
+    // specified.
+    DOMString[]? files;
+
     // The style origin for the injection. Defaults to <code>'AUTHOR'</code>.
     StyleOrigin? origin;
   };
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 88b9421..23671f6 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1039,6 +1039,7 @@
       "../browser/federated_learning/floc_id_provider_browsertest.cc",
       "../browser/federated_learning/floc_origin_trial_browsertest.cc",
       "../browser/first_run/first_run_browsertest.cc",
+      "../browser/font_access/font_access_context_browsertest.cc",
       "../browser/geolocation/geolocation_browsertest.cc",
       "../browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc",
       "../browser/heavy_ad_intervention/heavy_ad_helper_browsertest.cc",
@@ -3560,6 +3561,7 @@
     "../browser/password_manager/chrome_password_manager_client_unittest.cc",
     "../browser/performance_hints/performance_hints_observer_unittest.cc",
     "../browser/performance_hints/rewrite_handler_unittest.cc",
+    "../browser/performance_manager/decorators/execution_context_priority/frame_visibility_voter_unittest.cc",
     "../browser/performance_manager/decorators/execution_context_priority/root_vote_observer_unittest.cc",
     "../browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc",
     "../browser/performance_manager/decorators/helpers/page_live_state_decorator_helper_unittest.cc",
@@ -3673,8 +3675,8 @@
     "../browser/resources_util_unittest.cc",
     "../browser/security_events/security_event_recorder_impl_unittest.cc",
     "../browser/signin/e2e_tests/test_accounts_util_unittest.cc",
-    "../browser/subresource_redirect/https_image_compression_bypass_decider_unittest.cc",
     "../browser/subresource_redirect/https_image_compression_infobar_decider_unittest.cc",
+    "../browser/subresource_redirect/litepages_service_bypass_decider_unittest.cc",
     "../browser/subresource_redirect/origin_robots_rules_unittest.cc",
     "../browser/subresource_redirect/subresource_redirect_util_unit_test.cc",
 
diff --git a/chrome/test/chromedriver/test/run_py_tests.pydeps b/chrome/test/chromedriver/test/run_py_tests.pydeps
index 97ff8cc..1780200 100644
--- a/chrome/test/chromedriver/test/run_py_tests.pydeps
+++ b/chrome/test/chromedriver/test/run_py_tests.pydeps
@@ -4,6 +4,7 @@
 ../../../../build/android/pylib/__init__.py
 ../../../../build/android/pylib/constants/__init__.py
 ../../../../build/android/pylib/constants/host_paths.py
+../../../../build/gn_helpers.py
 ../../../../third_party/catapult/common/py_utils/py_utils/__init__.py
 ../../../../third_party/catapult/common/py_utils/py_utils/cloud_storage.py
 ../../../../third_party/catapult/common/py_utils/py_utils/cloud_storage_global_lock.py
diff --git a/chrome/test/data/extensions/api_test/scripting/css_injection/css_file.css b/chrome/test/data/extensions/api_test/scripting/css_injection/css_file.css
new file mode 100644
index 0000000..bf45675
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/scripting/css_injection/css_file.css
@@ -0,0 +1,5 @@
+/* Copyright 2020 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+body { background-color: yellow !important }
diff --git a/chrome/test/data/extensions/api_test/scripting/css_injection/worker.js b/chrome/test/data/extensions/api_test/scripting/css_injection/worker.js
index c4e4a64..e794da00 100644
--- a/chrome/test/data/extensions/api_test/scripting/css_injection/worker.js
+++ b/chrome/test/data/extensions/api_test/scripting/css_injection/worker.js
@@ -10,6 +10,9 @@
 const BLUE = 'rgb(0, 0, 255)';
 const CSS_CYAN = 'body { background-color: cyan !important }';
 const CYAN = 'rgb(0, 255, 255)';
+const YELLOW = 'rgb(255, 255, 0)';
+
+const EXACTLY_ONE_FILE_ERROR = 'Exactly one file must be specified.';
 
 function getBodyColor() {
   const hostname = (new URL(location.href)).hostname;
@@ -46,7 +49,7 @@
   // tabs to ensure a "clean slate", but it's not worth the added complexity
   // yet.
   // Instead, each test uses a different color.
-  async function changeBackground() {
+  async function changeBackgroundFromString() {
     const query = {url: 'http://example.com/*'};
     const tab = await getSingleTab(query);
     const results = await new Promise(resolve => {
@@ -127,6 +130,27 @@
     chrome.test.succeed();
   },
 
+  async function changeBackgroundFromFile() {
+    const query = {url: 'http://example.com/*'};
+    const tab = await getSingleTab(query);
+    const results = await new Promise(resolve => {
+      chrome.scripting.insertCSS(
+          {
+            target: {
+              tabId: tab.id,
+            },
+            files: ['css_file.css'],
+          },
+          resolve);
+    });
+    chrome.test.assertNoLastError();
+    chrome.test.assertEq(undefined, results);
+    const colors = await getBodyColorsForTab(tab.id);
+    chrome.test.assertEq(1, colors.length);
+    chrome.test.assertEq(`example.com ${YELLOW}`, colors[0]);
+    chrome.test.succeed();
+  },
+
   async function noSuchTab() {
     const nonExistentTabId = 99999;
     // NOTE(devlin): We can't use a fancy `await` here, because the lastError
@@ -146,6 +170,61 @@
         });
   },
 
+  async function noSuchFile() {
+    const noSuchFile = 'no_such_file.css';
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    // NOTE(devlin): We can't use a fancy `await` here, because the lastError
+    // won't be properly set. This will work better with true promise support,
+    // where this could be wrapped in an e.g. expectThrows().
+    chrome.scripting.insertCSS(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: [noSuchFile],
+        },
+        results => {
+          chrome.test.assertLastError(`Could not load file: '${noSuchFile}'.`);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
+  async function noFilesSpecified() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    chrome.scripting.executeScript(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: [],
+        },
+        results => {
+          chrome.test.assertLastError(EXACTLY_ONE_FILE_ERROR);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
+  async function multipleFilesSpecified() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    chrome.scripting.executeScript(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: ['css_file.css', 'css_file2.css'],
+        },
+        results => {
+          chrome.test.assertLastError(EXACTLY_ONE_FILE_ERROR);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
   async function disallowedPermission() {
     const query = {url: 'http://chromium.org/*'};
     const tab = await getSingleTab(query);
diff --git a/third_party/blink/renderer/modules/serial/serial_connection_event_init.idl b/chrome/test/data/extensions/api_test/scripting/main_frame/script_file.js
similarity index 65%
rename from third_party/blink/renderer/modules/serial/serial_connection_event_init.idl
rename to chrome/test/data/extensions/api_test/scripting/main_frame/script_file.js
index 70c2c0d..974ec68 100644
--- a/third_party/blink/renderer/modules/serial/serial_connection_event_init.idl
+++ b/chrome/test/data/extensions/api_test/scripting/main_frame/script_file.js
@@ -2,6 +2,5 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-dictionary SerialConnectionEventInit : EventInit {
-    required SerialPort port;
-};
+document.title = 'Goodnight';
+document.title;
diff --git a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
index e1a3eb0..43e59a0 100644
--- a/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
+++ b/chrome/test/data/extensions/api_test/scripting/main_frame/worker.js
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-const NEW_TITLE = 'Hello, world!';
+const NEW_TITLE_FROM_FUNCTION = 'Hello, world!';
+const NEW_TITLE_FROM_FILE = 'Goodnight';
+const EXACTLY_ONE_FILE_ERROR = 'Exactly one file must be specified.';
 
 function injectedFunction() {
   // NOTE(devlin): We currently need to (re)hard-code this title, since the
@@ -21,7 +23,7 @@
 }
 
 chrome.test.runTests([
-  async function changeTitle() {
+  async function changeTitleFromFunction() {
     const query = {url: 'http://example.com/*'};
     let tab = await getSingleTab(query);
     const results = await new Promise(resolve => {
@@ -36,9 +38,30 @@
     });
     chrome.test.assertNoLastError();
     chrome.test.assertEq(1, results.length);
-    chrome.test.assertEq(NEW_TITLE, results[0].result);
+    chrome.test.assertEq(NEW_TITLE_FROM_FUNCTION, results[0].result);
     tab = await getSingleTab(query);
-    chrome.test.assertEq(NEW_TITLE, tab.title);
+    chrome.test.assertEq(NEW_TITLE_FROM_FUNCTION, tab.title);
+    chrome.test.succeed();
+  },
+
+  async function changeTitleFromFile() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    const results = await new Promise(resolve => {
+      chrome.scripting.executeScript(
+          {
+            target: {
+              tabId: tab.id,
+            },
+            files: ['script_file.js'],
+          },
+          resolve);
+    });
+    chrome.test.assertNoLastError();
+    chrome.test.assertEq(1, results.length);
+    chrome.test.assertEq(NEW_TITLE_FROM_FILE, results[0].result);
+    tab = await getSingleTab(query);
+    chrome.test.assertEq(NEW_TITLE_FROM_FILE, tab.title);
     chrome.test.succeed();
   },
 
@@ -54,13 +77,65 @@
           },
           function: injectedFunction,
         },
-        async results => {
+        results => {
           chrome.test.assertLastError(`No tab with id: ${nonExistentTabId}`);
           chrome.test.assertEq(undefined, results);
           chrome.test.succeed();
         });
   },
 
+  async function noSuchFile() {
+    const noSuchFile = 'no_such_file.js';
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    chrome.scripting.executeScript(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: [noSuchFile],
+        },
+        results => {
+          chrome.test.assertLastError(`Could not load file: '${noSuchFile}'.`);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
+  async function noFilesSpecified() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    chrome.scripting.executeScript(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: [],
+        },
+        results => {
+          chrome.test.assertLastError(EXACTLY_ONE_FILE_ERROR);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
+  async function multipleFilesSpecified() {
+    const query = {url: 'http://example.com/*'};
+    let tab = await getSingleTab(query);
+    chrome.scripting.executeScript(
+        {
+          target: {
+            tabId: tab.id,
+          },
+          files: ['script_file.js', 'script_file2.js'],
+        },
+        results => {
+          chrome.test.assertLastError(EXACTLY_ONE_FILE_ERROR);
+          chrome.test.assertEq(undefined, results);
+          chrome.test.succeed();
+        });
+  },
+
   async function disallowedPermission() {
     const query = {url: 'http://chromium.org/*'};
     let tab = await getSingleTab(query);
diff --git a/chrome/test/data/password/cleared_change_password_forms.html b/chrome/test/data/password/cleared_change_password_forms.html
new file mode 100644
index 0000000..dcfce63
--- /dev/null
+++ b/chrome/test/data/password/cleared_change_password_forms.html
@@ -0,0 +1,42 @@
+<html>
+<body>
+<!-- Change password form with username. -->
+<form action="done.html" id="chg_testform">
+  <input type="text" id="chg_username_field" name="chg_username_field">
+  <input type="password" id="chg_password_field" name="chg_password_field">
+  <input type="password" id="chg_new_password_1" name="chg_new_password_1">
+  <input type="password" id="chg_new_password_2" name="chg_new_password_2">
+  <input type="submit" id="chg_submit_button" name="chg_submit_button">
+</form>
+
+<button id="chg_clear_button" name="chg_clear_button"
+        onclick="document.getElementById('chg_testform').reset()">
+  Clear the form on successful JS submission
+</button>
+
+<button id="chg_clear_all_fields_button" name="chg_clear_all_fields_button"
+        onclick="document.getElementById('chg_password_field').value = '';document.getElementById('chg_new_password_1').value = '';document.getElementById('chg_new_password_2').value = '';">
+  Clear all form fields on successful JS submission
+</button>
+
+<button id="chg_clear_some_fields_button" name="chg_clear_some_fields_button"
+        onclick="document.getElementById('chg_password_field').value = '';document.getElementById('chg_new_password_1').value = '';">
+  Clear some form fields
+</button>
+
+<!-- Formless change password fields. -->
+<input type="password" id="formless_chg_password_field">
+<input type="password" id="formless_chg_new_password_1" autocomplete="new-password">
+<input type="password" id="formless_chg_new_password_2">
+
+<button id="chg_clear_all_formless_fields_button"
+        onclick="document.getElementById('formless_chg_password_field').value = '';document.getElementById('formless_chg_new_password_1').value = '';document.getElementById('formless_chg_new_password_2').value = '';">
+  Clear relevant formless fields on successful submission
+</button>
+
+<button id="chg_clear_some_formless_fields_button"
+        onclick="document.getElementById('formless_chg_password_field').value = '';document.getElementById('formless_chg_new_password_2').value = '';">
+  Clear some formless fields
+</button>
+</body>
+</html>
diff --git a/chrome/test/data/password/password_form.html b/chrome/test/data/password/password_form.html
index c089489..3239107 100644
--- a/chrome/test/data/password/password_form.html
+++ b/chrome/test/data/password/password_form.html
@@ -74,7 +74,6 @@
 
 <!-- Change password form with username. -->
 <form action="done.html" id="chg_testform">
-
   <input type="text" id="chg_username_field" name="chg_username_field">
   <input type="password" id="chg_password_field" name="chg_password_field">
   <input type="password" id="chg_new_password_1" name="chg_new_password_1">
@@ -82,11 +81,6 @@
   <input type="submit" id="chg_submit_button" name="chg_submit_button">
 </form>
 
-<button id="chg_clear_button" name="chg_clear_button"
-        onclick="document.getElementById('chg_testform').reset()">
-  Clear the form on successful JS submission!
-</button>
-
 <!-- Change password form without the username. -->
 <form action="done.html" id="chg_testform_wo_username">
   <input type="password" id="chg_password_wo_username_field" name="chg_password_wo_username_field">
diff --git a/chrome/updater/app/server/win/BUILD.gn b/chrome/updater/app/server/win/BUILD.gn
index 8eb5f25..1a8dbd4 100644
--- a/chrome/updater/app/server/win/BUILD.gn
+++ b/chrome/updater/app/server/win/BUILD.gn
@@ -35,9 +35,9 @@
 # These GUIDs must depend on branding and version.
 branding_version_placeholder_guids = [
   "PLACEHOLDER-GUID-C6CE92DB-72CA-42EF-8C98-6EE92481B3C9",  # UpdaterInternalLib
-  "PLACEHOLDER-GUID-1F87FE2F-D6A9-4711-9D11-8187705F8457",  # UpdaterControlClass
-  "PLACEHOLDER-GUID-526DA036-9BD3-4697-865A-DA12D37DFFCA",  # IUpdaterControl
-  "PLACEHOLDER-GUID-D272C794-2ACE-4584-B993-3B90C622BE65",  # IUpdaterControlCallback
+  "PLACEHOLDER-GUID-1F87FE2F-D6A9-4711-9D11-8187705F8457",  # UpdaterInternalClass
+  "PLACEHOLDER-GUID-526DA036-9BD3-4697-865A-DA12D37DFFCA",  # IUpdaterInternal
+  "PLACEHOLDER-GUID-D272C794-2ACE-4584-B993-3B90C622BE65",  # IUpdaterInternalCallback
 ]
 
 uuid5_guids = []
diff --git a/chrome/updater/app/server/win/com_classes.cc b/chrome/updater/app/server/win/com_classes.cc
index d4afffb..8b4dd55 100644
--- a/chrome/updater/app/server/win/com_classes.cc
+++ b/chrome/updater/app/server/win/com_classes.cc
@@ -227,9 +227,9 @@
 }
 
 // See the comment for the UpdaterImpl::Update.
-HRESULT UpdaterControlImpl::Run(IUpdaterControlCallback* callback) {
-  using IUpdaterControlCallbackPtr =
-      Microsoft::WRL::ComPtr<IUpdaterControlCallback>;
+HRESULT UpdaterInternalImpl::Run(IUpdaterInternalCallback* callback) {
+  using IUpdaterInternalCallbackPtr =
+      Microsoft::WRL::ComPtr<IUpdaterInternalCallback>;
   scoped_refptr<ComServerApp> com_server = AppServerSingletonInstance();
 
   auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
@@ -240,33 +240,33 @@
       base::BindOnce(
           [](scoped_refptr<UpdateServiceInternal> update_service_internal,
              scoped_refptr<base::SequencedTaskRunner> task_runner,
-             IUpdaterControlCallbackPtr callback) {
+             IUpdaterInternalCallbackPtr callback) {
             update_service_internal->Run(base::BindOnce(
                 [](scoped_refptr<base::SequencedTaskRunner> task_runner,
-                   IUpdaterControlCallbackPtr callback) {
+                   IUpdaterInternalCallbackPtr callback) {
                   task_runner->PostTaskAndReplyWithResult(
                       FROM_HERE,
-                      base::BindOnce(&IUpdaterControlCallback::Run, callback,
+                      base::BindOnce(&IUpdaterInternalCallback::Run, callback,
                                      0),
                       base::BindOnce([](HRESULT hr) {
-                        DVLOG(2) << "UpdaterControlImpl::Run "
+                        DVLOG(2) << "UpdaterInternalImpl::Run "
                                  << "callback returned " << std::hex << hr;
                       }));
                 },
                 task_runner, callback));
           },
           com_server->update_service_internal(), task_runner,
-          IUpdaterControlCallbackPtr(callback)));
+          IUpdaterInternalCallbackPtr(callback)));
 
   // Always return S_OK from this function. Errors must be reported using the
   // callback interface.
   return S_OK;
 }
 
-HRESULT UpdaterControlImpl::InitializeUpdateService(
-    IUpdaterControlCallback* callback) {
-  using IUpdaterControlCallbackPtr =
-      Microsoft::WRL::ComPtr<IUpdaterControlCallback>;
+HRESULT UpdaterInternalImpl::InitializeUpdateService(
+    IUpdaterInternalCallback* callback) {
+  using IUpdaterInternalCallbackPtr =
+      Microsoft::WRL::ComPtr<IUpdaterInternalCallback>;
   scoped_refptr<ComServerApp> com_server = AppServerSingletonInstance();
 
   auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
@@ -277,24 +277,24 @@
       base::BindOnce(
           [](scoped_refptr<UpdateServiceInternal> update_service_internal,
              scoped_refptr<base::SequencedTaskRunner> task_runner,
-             IUpdaterControlCallbackPtr callback) {
+             IUpdaterInternalCallbackPtr callback) {
             update_service_internal->InitializeUpdateService(base::BindOnce(
                 [](scoped_refptr<base::SequencedTaskRunner> task_runner,
-                   IUpdaterControlCallbackPtr callback) {
+                   IUpdaterInternalCallbackPtr callback) {
                   task_runner->PostTaskAndReplyWithResult(
                       FROM_HERE,
-                      base::BindOnce(&IUpdaterControlCallback::Run, callback,
+                      base::BindOnce(&IUpdaterInternalCallback::Run, callback,
                                      0),
                       base::BindOnce([](HRESULT hr) {
                         DVLOG(2)
-                            << "UpdaterControlImpl::InitializeUpdateService "
+                            << "UpdaterInternalImpl::InitializeUpdateService "
                             << "callback returned " << std::hex << hr;
                       }));
                 },
                 task_runner, callback));
           },
           com_server->update_service_internal(), task_runner,
-          IUpdaterControlCallbackPtr(callback)));
+          IUpdaterInternalCallbackPtr(callback)));
 
   // Always return S_OK from this function. Errors must be reported using the
   // callback interface.
diff --git a/chrome/updater/app/server/win/com_classes.h b/chrome/updater/app/server/win/com_classes.h
index 160de00..35a0dae 100644
--- a/chrome/updater/app/server/win/com_classes.h
+++ b/chrome/updater/app/server/win/com_classes.h
@@ -102,24 +102,24 @@
   ~UpdaterImpl() override = default;
 };
 
-// This class implements the IUpdaterControl interface and exposes it as a COM
+// This class implements the IUpdaterInternal interface and exposes it as a COM
 // object.
-class UpdaterControlImpl
+class UpdaterInternalImpl
     : public Microsoft::WRL::RuntimeClass<
           Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          IUpdaterControl> {
+          IUpdaterInternal> {
  public:
-  UpdaterControlImpl() = default;
-  UpdaterControlImpl(const UpdaterControlImpl&) = delete;
-  UpdaterControlImpl& operator=(const UpdaterControlImpl&) = delete;
+  UpdaterInternalImpl() = default;
+  UpdaterInternalImpl(const UpdaterInternalImpl&) = delete;
+  UpdaterInternalImpl& operator=(const UpdaterInternalImpl&) = delete;
 
-  // Overrides for IUpdaterControl.
-  IFACEMETHODIMP Run(IUpdaterControlCallback* callback) override;
+  // Overrides for IUpdaterInternal.
+  IFACEMETHODIMP Run(IUpdaterInternalCallback* callback) override;
   IFACEMETHODIMP InitializeUpdateService(
-      IUpdaterControlCallback* callback) override;
+      IUpdaterInternalCallback* callback) override;
 
  private:
-  ~UpdaterControlImpl() override = default;
+  ~UpdaterInternalImpl() override = default;
 };
 
 }  // namespace updater
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index 254481a..48b0392 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -75,15 +75,15 @@
   factory.Reset();
 
   hr = Microsoft::WRL::Details::CreateClassFactory<
-      Microsoft::WRL::SimpleClassFactory<UpdaterControlImpl>>(
+      Microsoft::WRL::SimpleClassFactory<UpdaterInternalImpl>>(
       &flags, nullptr, __uuidof(IClassFactory), &factory);
   if (FAILED(hr)) {
-    LOG(ERROR) << "Factory creation for UpdaterControlImpl failed; hr: " << hr;
+    LOG(ERROR) << "Factory creation for UpdaterInternalImpl failed; hr: " << hr;
     return hr;
   }
 
-  Microsoft::WRL::ComPtr<IClassFactory> class_factory_updater_control;
-  hr = factory.As(&class_factory_updater_control);
+  Microsoft::WRL::ComPtr<IClassFactory> class_factory_updater_internal;
+  hr = factory.As(&class_factory_updater_internal);
   if (FAILED(hr)) {
     LOG(ERROR) << "IClassFactory object creation failed; hr: " << hr;
     return hr;
@@ -107,13 +107,13 @@
 
   // The pointer in this array is unowned. Do not release it.
   IClassFactory* class_factories[] = {class_factory_updater.Get(),
-                                      class_factory_updater_control.Get(),
+                                      class_factory_updater_internal.Get(),
                                       class_factory_legacy_ondemand.Get()};
   static_assert(
       std::extent<decltype(cookies_)>() == base::size(class_factories),
       "Arrays cookies_ and class_factories must be the same size.");
 
-  IID class_ids[] = {__uuidof(UpdaterClass), __uuidof(UpdaterControlClass),
+  IID class_ids[] = {__uuidof(UpdaterClass), __uuidof(UpdaterInternalClass),
                      __uuidof(GoogleUpdate3WebUserClass)};
   DCHECK_EQ(base::size(cookies_), base::size(class_ids));
   static_assert(std::extent<decltype(cookies_)>() == base::size(class_ids),
diff --git a/chrome/updater/app/server/win/updater_internal_idl.template b/chrome/updater/app/server/win/updater_internal_idl.template
index e1191c3..cfb0693 100644
--- a/chrome/updater/app/server/win/updater_internal_idl.template
+++ b/chrome/updater/app/server/win/updater_internal_idl.template
@@ -13,10 +13,10 @@
   object,
   dual,
   uuid(PLACEHOLDER-GUID-D272C794-2ACE-4584-B993-3B90C622BE65),
-  helpstring("IUpdaterControlCallback Interface"),
+  helpstring("IUpdaterInternalCallback Interface"),
   pointer_default(unique)
 ]
-interface IUpdaterControlCallback : IUnknown {
+interface IUpdaterInternalCallback : IUnknown {
   HRESULT Run([in] LONG result);
 };
 
@@ -24,12 +24,12 @@
   object,
   dual,
   uuid(PLACEHOLDER-GUID-526DA036-9BD3-4697-865A-DA12D37DFFCA),
-  helpstring("IUpdaterControl Interface"),
+  helpstring("IUpdaterInternal Interface"),
   pointer_default(unique)
 ]
-interface IUpdaterControl : IUnknown {
-  HRESULT Run([in] IUpdaterControlCallback* callback);
-  HRESULT InitializeUpdateService([in] IUpdaterControlCallback* callback);
+interface IUpdaterInternal : IUnknown {
+  HRESULT Run([in] IUpdaterInternalCallback* callback);
+  HRESULT InitializeUpdateService([in] IUpdaterInternalCallback* callback);
 };
 
 [
@@ -42,13 +42,13 @@
 
   [
     uuid(PLACEHOLDER-GUID-1F87FE2F-D6A9-4711-9D11-8187705F8457),
-    helpstring("UpdaterControl Class")
+    helpstring("UpdaterInternal Class")
   ]
-  coclass UpdaterControlClass
+  coclass UpdaterInternalClass
   {
     [default] interface IUnknown;
   }
 
-  interface IUpdaterControl;
-  interface IUpdaterControlCallback;
+  interface IUpdaterInternal;
+  interface IUpdaterInternalCallback;
 };
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 98cba3b..c24409d 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -168,13 +168,13 @@
   Microsoft::WRL::ComPtr<IUpdater> updater;
   EXPECT_HRESULT_SUCCEEDED(updater_server.As(&updater));
 
-  // IUpdaterControl.
-  Microsoft::WRL::ComPtr<IUnknown> updater_control_server;
+  // IUpdaterInternal.
+  Microsoft::WRL::ComPtr<IUnknown> updater_internal_server;
   EXPECT_HRESULT_SUCCEEDED(::CoCreateInstance(
-      __uuidof(UpdaterControlClass), nullptr, CLSCTX_LOCAL_SERVER,
-      IID_PPV_ARGS(&updater_control_server)));
-  Microsoft::WRL::ComPtr<IUpdaterControl> updater_control;
-  EXPECT_HRESULT_SUCCEEDED(updater_control_server.As(&updater_control));
+      __uuidof(UpdaterInternalClass), nullptr, CLSCTX_LOCAL_SERVER,
+      IID_PPV_ARGS(&updater_internal_server)));
+  Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
+  EXPECT_HRESULT_SUCCEEDED(updater_internal_server.As(&updater_internal));
 
   // IGoogleUpdate3Web and IAppBundleWeb.
   Microsoft::WRL::ComPtr<IUnknown> updater_legacy_server;
diff --git a/chrome/updater/win/control_service_proxy.cc b/chrome/updater/win/control_service_proxy.cc
index b3eb11d..98a2983 100644
--- a/chrome/updater/win/control_service_proxy.cc
+++ b/chrome/updater/win/control_service_proxy.cc
@@ -24,22 +24,22 @@
     base::TaskPriority::BEST_EFFORT,
     base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
 
-// This class implements the IUpdaterControlCallback interface and exposes it as
-// a COM object. The class has thread-affinity for the STA thread.
-class UpdaterControlCallback
+// This class implements the IUpdaterInternalCallback interface and exposes it
+// as a COM object. The class has thread-affinity for the STA thread.
+class UpdaterInternalCallback
     : public Microsoft::WRL::RuntimeClass<
           Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          IUpdaterControlCallback> {
+          IUpdaterInternalCallback> {
  public:
-  UpdaterControlCallback(
-      Microsoft::WRL::ComPtr<IUpdaterControl> updater_control,
+  UpdaterInternalCallback(
+      Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal,
       base::OnceClosure callback)
-      : updater_control_(updater_control), callback_(std::move(callback)) {}
+      : updater_internal_(updater_internal), callback_(std::move(callback)) {}
 
-  UpdaterControlCallback(const UpdaterControlCallback&) = delete;
-  UpdaterControlCallback& operator=(const UpdaterControlCallback&) = delete;
+  UpdaterInternalCallback(const UpdaterInternalCallback&) = delete;
+  UpdaterInternalCallback& operator=(const UpdaterInternalCallback&) = delete;
 
-  // Overrides for IUpdaterControlCallback.
+  // Overrides for IUpdaterInternalCallback.
   //
   // Invoked by COM RPC on the apartment thread (STA) when the call to any of
   // the non-blocking `UpdateServiceInternalProxy` functions completes.
@@ -51,7 +51,7 @@
   base::OnceClosure Disconnect();
 
  private:
-  ~UpdaterControlCallback() override = default;
+  ~UpdaterInternalCallback() override = default;
 
   void RunOnSTA();
 
@@ -68,13 +68,13 @@
 
   // Keeps a reference of the updater object alive, while this object is
   // owned by the COM RPC runtime.
-  Microsoft::WRL::ComPtr<IUpdaterControl> updater_control_;
+  Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal_;
 
-  // Called by IUpdaterControlCallback::Run when the COM RPC call is done.
+  // Called by IUpdaterInternalCallback::Run when the COM RPC call is done.
   base::OnceClosure callback_;
 };
 
-IFACEMETHODIMP UpdaterControlCallback::Run(LONG result) {
+IFACEMETHODIMP UpdaterInternalCallback::Run(LONG result) {
   DVLOG(2) << __func__ << " result " << result << ".";
 
   // Since this function is invoked directly by COM RPC, the code can only
@@ -83,22 +83,22 @@
   // which is sequenced by `STA_task_runner`.
   DCHECK_EQ(base::PlatformThread::CurrentId(), STA_thread_id_);
   STA_task_runner_->PostTask(FROM_HERE,
-                             base::BindOnce(&UpdaterControlCallback::RunOnSTA,
+                             base::BindOnce(&UpdaterInternalCallback::RunOnSTA,
                                             base::WrapRefCounted(this)));
   return S_OK;
 }
 
-base::OnceClosure UpdaterControlCallback::Disconnect() {
+base::OnceClosure UpdaterInternalCallback::Disconnect() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOG(2) << __func__;
-  updater_control_ = nullptr;
+  updater_internal_ = nullptr;
   return std::move(callback_);
 }
 
-void UpdaterControlCallback::RunOnSTA() {
+void UpdaterInternalCallback::RunOnSTA() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  updater_control_ = nullptr;
+  updater_internal_ = nullptr;
 
   if (!callback_) {
     DVLOG(2) << "Skipping posting the completion callback.";
@@ -138,19 +138,19 @@
   DCHECK(STA_task_runner_->BelongsToCurrentThread());
 
   Microsoft::WRL::ComPtr<IUnknown> server;
-  HRESULT hr = ::CoCreateInstance(__uuidof(UpdaterControlClass), nullptr,
+  HRESULT hr = ::CoCreateInstance(__uuidof(UpdaterInternalClass), nullptr,
                                   CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&server));
   if (FAILED(hr)) {
-    DVLOG(2) << "Failed to instantiate the updater control server. " << std::hex
-             << hr;
+    DVLOG(2) << "Failed to instantiate the updater internal server. "
+             << std::hex << hr;
     std::move(callback).Run();
     return;
   }
 
-  Microsoft::WRL::ComPtr<IUpdaterControl> updater_control;
-  hr = server.As(&updater_control);
+  Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
+  hr = server.As(&updater_internal);
   if (FAILED(hr)) {
-    DVLOG(2) << "Failed to query the updater_control interface. " << std::hex
+    DVLOG(2) << "Failed to query the updater_internal interface. " << std::hex
              << hr;
     std::move(callback).Run();
     return;
@@ -158,17 +158,17 @@
 
   // The `rpc_callback` takes ownership of the `callback` and owns a reference
   // to the updater object as well. As long as the `rpc_callback` retains this
-  // reference to the updater control object, then the object is going to stay
+  // reference to the updater internal object, then the object is going to stay
   // alive.
-  // The `rpc_callback` drops its reference to the updater control object when
+  // The `rpc_callback` drops its reference to the updater internal object when
   // handling the last server callback. After that, the object model is torn
   // down, and the execution flow returns back into the App object when
   // `callback` is posted.
-  auto rpc_callback = Microsoft::WRL::Make<UpdaterControlCallback>(
-      updater_control, std::move(callback));
-  hr = updater_control->Run(rpc_callback.Get());
+  auto rpc_callback = Microsoft::WRL::Make<UpdaterInternalCallback>(
+      updater_internal, std::move(callback));
+  hr = updater_internal->Run(rpc_callback.Get());
   if (FAILED(hr)) {
-    DVLOG(2) << "Failed to call IUpdaterControl::Run" << std::hex << hr;
+    DVLOG(2) << "Failed to call IUpdaterInternal::Run" << std::hex << hr;
 
     // Since the RPC call returned an error, it can't be determined what the
     // state of the update server is. The RPC callback may or may not have run.
@@ -201,29 +201,29 @@
   DCHECK(STA_task_runner_->BelongsToCurrentThread());
 
   Microsoft::WRL::ComPtr<IUnknown> server;
-  HRESULT hr = ::CoCreateInstance(__uuidof(UpdaterControlClass), nullptr,
+  HRESULT hr = ::CoCreateInstance(__uuidof(UpdaterInternalClass), nullptr,
                                   CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&server));
   if (FAILED(hr)) {
-    DVLOG(2) << "Failed to instantiate the updater control server. " << std::hex
+    DVLOG(2) << "Failed to instantiate the updater internal server. "
+             << std::hex << hr;
+    std::move(callback).Run();
+    return;
+  }
+
+  Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
+  hr = server.As(&updater_internal);
+  if (FAILED(hr)) {
+    DVLOG(2) << "Failed to query the updater_internal interface. " << std::hex
              << hr;
     std::move(callback).Run();
     return;
   }
 
-  Microsoft::WRL::ComPtr<IUpdaterControl> updater_control;
-  hr = server.As(&updater_control);
+  auto rpc_callback = Microsoft::WRL::Make<UpdaterInternalCallback>(
+      updater_internal, std::move(callback));
+  hr = updater_internal->InitializeUpdateService(rpc_callback.Get());
   if (FAILED(hr)) {
-    DVLOG(2) << "Failed to query the updater_control interface. " << std::hex
-             << hr;
-    std::move(callback).Run();
-    return;
-  }
-
-  auto rpc_callback = Microsoft::WRL::Make<UpdaterControlCallback>(
-      updater_control, std::move(callback));
-  hr = updater_control->InitializeUpdateService(rpc_callback.Get());
-  if (FAILED(hr)) {
-    DVLOG(2) << "Failed to call IUpdaterControl::InitializeUpdateService"
+    DVLOG(2) << "Failed to call IUpdaterInternal::InitializeUpdateService"
              << std::hex << hr;
     rpc_callback->Disconnect().Run();
     return;
diff --git a/chrome/updater/win/setup/setup.cc b/chrome/updater/win/setup/setup.cc
index 8de37ae2..8a9c36b 100644
--- a/chrome/updater/win/setup/setup.cc
+++ b/chrome/updater/win/setup/setup.cc
@@ -51,7 +51,7 @@
   }
 
   for (const auto& clsid :
-       {__uuidof(UpdaterClass), __uuidof(UpdaterControlClass),
+       {__uuidof(UpdaterClass), __uuidof(UpdaterInternalClass),
         __uuidof(GoogleUpdate3WebUserClass)}) {
     const base::string16 clsid_reg_path = GetComServerClsidRegistryPath(clsid);
 
diff --git a/chrome/updater/win/setup/setup_util.cc b/chrome/updater/win/setup/setup_util.cc
index af90e87..e47ef90 100644
--- a/chrome/updater/win/setup/setup_util.cc
+++ b/chrome/updater/win/setup/setup_util.cc
@@ -115,8 +115,8 @@
       {__uuidof(IUpdateState), kUpdaterIndex},
 
       // Updater internal typelib.
-      {__uuidof(IUpdaterControl), kUpdaterInternalIndex},
-      {__uuidof(IUpdaterControlCallback), kUpdaterInternalIndex},
+      {__uuidof(IUpdaterInternal), kUpdaterInternalIndex},
+      {__uuidof(IUpdaterInternalCallback), kUpdaterInternalIndex},
 
       // Updater legacy typelib.
       {__uuidof(IAppBundleWeb), kUpdaterLegacyIndex},
@@ -137,8 +137,8 @@
       __uuidof(IGoogleUpdate3Web),
       __uuidof(IUpdateState),
       __uuidof(IUpdater),
-      __uuidof(IUpdaterControl),
-      __uuidof(IUpdaterControlCallback),
+      __uuidof(IUpdaterInternal),
+      __uuidof(IUpdaterInternalCallback),
       __uuidof(IUpdaterObserver),
   };
   return kInterfaces;
diff --git a/chrome/updater/win/setup/uninstall.cc b/chrome/updater/win/setup/uninstall.cc
index badf33c..0648554 100644
--- a/chrome/updater/win/setup/uninstall.cc
+++ b/chrome/updater/win/setup/uninstall.cc
@@ -36,7 +36,7 @@
 
 void DeleteComServer(HKEY root) {
   for (const auto& clsid :
-       {__uuidof(UpdaterClass), __uuidof(UpdaterControlClass),
+       {__uuidof(UpdaterClass), __uuidof(UpdaterInternalClass),
         __uuidof(GoogleUpdate3WebUserClass)}) {
     InstallUtil::DeleteRegistryKey(root, GetComServerClsidRegistryPath(clsid),
                                    WorkItem::kWow64Default);
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 91bc55d6..0d7cd67 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -6,6 +6,7 @@
 import("//build/config/ui.gni")
 import("//chromecast/chromecast.gni")
 import("//media/media_options.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
 import("//testing/test.gni")
 import("//tools/grit/grit_rule.gni")
 
@@ -540,6 +541,11 @@
   ]
 }
 
+mojom("test_interfaces") {
+  testonly = true
+  sources = [ "test_interfaces.test-mojom" ]
+}
+
 cast_source_set("browsertests") {
   testonly = true
   sources = [
@@ -555,6 +561,7 @@
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
 
   deps = [
+    ":test_interfaces",
     ":test_support",
     "//base",
     "//chromecast:chromecast_buildflags",
@@ -567,6 +574,8 @@
     "//content/test:test_support",
     "//media:test_support",
     "//net:test_support",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/mojom",
   ]
 
   data = [
diff --git a/chromecast/browser/cast_browser_interface_binders.cc b/chromecast/browser/cast_browser_interface_binders.cc
index 5f66833..b33fd59 100644
--- a/chromecast/browser/cast_browser_interface_binders.cc
+++ b/chromecast/browser/cast_browser_interface_binders.cc
@@ -54,6 +54,26 @@
       ::media::mojom::Remotee::Name_, &interface_pipe);
 }
 
+// Some Cast internals still dynamically set up interface binders after
+// frame host initialization. This is used to generically forward incoming
+// interface receivers to those objects until they can be reworked as static
+// registrations below.
+bool HandleGenericReceiver(content::RenderFrameHost* frame_host,
+                           mojo::GenericPendingReceiver& receiver) {
+  auto* web_contents = content::WebContents::FromRenderFrameHost(frame_host);
+  if (!web_contents)
+    return false;
+
+  // Only WebContents created for Cast Webviews will have a CastWebContents
+  // object associated with them. We ignore these requests for any other
+  // WebContents.
+  auto* cast_web_contents = CastWebContents::FromWebContents(web_contents);
+  if (!cast_web_contents || !cast_web_contents->can_bind_interfaces())
+    return false;
+
+  return cast_web_contents->TryBindReceiver(receiver);
+}
+
 }  // namespace
 
 void PopulateCastFrameBinders(
@@ -65,6 +85,9 @@
       base::BindRepeating(&BindApplicationMediaCapabilities));
   binder_map->Add<::media::mojom::Remotee>(
       base::BindRepeating(&BindMediaRemotingRemotee));
+
+  binder_map->SetDefaultBinderDeprecated(
+      base::BindRepeating(&HandleGenericReceiver));
 }
 
 }  // namespace shell
diff --git a/chromecast/browser/cast_web_contents.h b/chromecast/browser/cast_web_contents.h
index 2cf04fa..b7fa8fb 100644
--- a/chromecast/browser/cast_web_contents.h
+++ b/chromecast/browser/cast_web_contents.h
@@ -16,6 +16,7 @@
 #include "base/strings/string16.h"
 #include "chromecast/common/mojom/feature_manager.mojom.h"
 #include "content/public/common/media_playback_renderer_type.mojom.h"
+#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/common/messaging/web_message_port.h"
@@ -380,6 +381,10 @@
   // when it is ready.
   virtual service_manager::BinderRegistry* binder_registry() = 0;
 
+  // Asks the CastWebContents to bind an interface receiver using either its
+  // registry or any registered InterfaceProvider.
+  virtual bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) = 0;
+
   // Used for owner to pass its |InterfaceProvider| pointers to CastWebContents.
   // It is owner's responsibility to make sure each |InterfaceProvider| pointer
   // has distinct mojo interface set.
diff --git a/chromecast/browser/cast_web_contents_browsertest.cc b/chromecast/browser/cast_web_contents_browsertest.cc
index 454478a..0e4e7bc 100644
--- a/chromecast/browser/cast_web_contents_browsertest.cc
+++ b/chromecast/browser/cast_web_contents_browsertest.cc
@@ -19,11 +19,13 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "chromecast/base/chromecast_switches.h"
 #include "chromecast/base/metrics/cast_metrics_helper.h"
 #include "chromecast/browser/cast_browser_context.h"
 #include "chromecast/browser/cast_browser_process.h"
 #include "chromecast/browser/cast_web_contents_impl.h"
+#include "chromecast/browser/test_interfaces.test-mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -33,11 +35,16 @@
 #include "content/public/test/browser_test_base.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
-#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "net/http/http_status_code.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+#include "services/service_manager/public/mojom/interface_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -239,8 +246,9 @@
     SetUpCommandLine(base::CommandLine::ForCurrentProcess());
     BrowserTestBase::SetUp();
   }
-  void SetUpCommandLine(base::CommandLine* command_line) final {
+  void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitchASCII(switches::kTestType, "browser");
+    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "MojoJS");
   }
   void PreRunTestOnMainThread() override {
     // Pump startup related events.
@@ -955,6 +963,147 @@
   run_loop2.Run();
 }
 
+// Helper for the test below. This exposes two interfaces, TestAdder and
+// TestDoubler. TestAdder is exposed only through a binder (see MakeAdderBinder)
+// which the test will register in the CastWebContents' binder_registry().
+// TestDoubler is exposed only through an InterfaceProvider, registered with the
+// CastWebContents using RegisterInterfaceProvider.
+class TestInterfaceProvider : public service_manager::mojom::InterfaceProvider,
+                              public mojom::TestAdder,
+                              public mojom::TestDoubler {
+ public:
+  TestInterfaceProvider()
+      : provider_(receiver_.BindNewPipeAndPassRemote(),
+                  base::SequencedTaskRunnerHandle::Get()) {}
+  ~TestInterfaceProvider() override = default;
+
+  size_t num_adders() const { return adders_.size(); }
+  size_t num_doublers() const { return doublers_.size(); }
+
+  service_manager::InterfaceProvider* interface_provider() {
+    return &provider_;
+  }
+
+  base::RepeatingCallback<void(mojo::PendingReceiver<mojom::TestAdder>)>
+  MakeAdderBinder() {
+    return base::BindLambdaForTesting(
+        [this](mojo::PendingReceiver<mojom::TestAdder> receiver) {
+          adders_.Add(this, std::move(receiver));
+          OnRequestHandled();
+        });
+  }
+
+  // Waits for some number of new interface binding requests to be dispatched
+  // and then invokes `callback`.
+  void WaitForRequests(size_t n, base::OnceClosure callback) {
+    wait_callback_ = std::move(callback);
+    num_requests_to_wait_for_ = n;
+  }
+
+  // service_manager::mojom::InterfaceProvider:
+  void GetInterface(const std::string& interface_name,
+                    mojo::ScopedMessagePipeHandle interface_pipe) override {
+    if (interface_name == mojom::TestDoubler::Name_) {
+      doublers_.Add(this, mojo::PendingReceiver<mojom::TestDoubler>(
+                              std::move(interface_pipe)));
+      OnRequestHandled();
+    }
+  }
+
+  // mojom::TestAdder:
+  void Add(int32_t a, int32_t b, AddCallback callback) override {
+    std::move(callback).Run(a + b);
+  }
+
+  // mojom::TestDouble:
+  void Double(int32_t x, DoubleCallback callback) override {
+    std::move(callback).Run(x * 2);
+  }
+
+ private:
+  void OnRequestHandled() {
+    if (num_requests_to_wait_for_ == 0)
+      return;
+    DCHECK(wait_callback_);
+    if (--num_requests_to_wait_for_ == 0)
+      std::move(wait_callback_).Run();
+  }
+
+  mojo::Receiver<service_manager::mojom::InterfaceProvider> receiver_{this};
+  service_manager::InterfaceProvider provider_;
+  mojo::ReceiverSet<mojom::TestAdder> adders_;
+  mojo::ReceiverSet<mojom::TestDoubler> doublers_;
+  size_t num_requests_to_wait_for_ = 0;
+  base::OnceClosure wait_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(CastWebContentsBrowserTest, InterfaceBinding) {
+  // This test verifies that interfaces registered with the CastWebContents --
+  // either via its binder_registry() or its RegisterInterfaceProvider() API --
+  // are reachable from render frames using either the deprecated
+  // InterfaceProvider API (which results in an OnInterfaceRequestFromFrame call
+  // on the WebContents) or the newer BrowserInterfaceBroker API which is used
+  // in most other places (including from Mojo JS).
+  TestInterfaceProvider provider;
+  cast_web_contents_->binder_registry()->AddInterface(
+      provider.MakeAdderBinder());
+  cast_web_contents_->RegisterInterfaceProvider(
+      CastWebContents::InterfaceSet{mojom::TestDoubler::Name_},
+      provider.interface_provider());
+
+  // First verify that both interfaces are reachable using the deprecated
+  // WebContents path, which is triggered only by renderer-side use of
+  // RenderFrame::GetRemoteInterfaces(). Since poking renderer state in browser
+  // tests is challenging, we simply simulate the resulting WebContentsObbserver
+  // calls here instead and verify end-to-end connection for each interface.
+  content::RenderFrameHost* main_frame =
+      cast_web_contents_->web_contents()->GetMainFrame();
+  mojo::Remote<mojom::TestAdder> adder;
+  mojo::ScopedMessagePipeHandle adder_receiver_pipe =
+      adder.BindNewPipeAndPassReceiver().PassPipe();
+  cast_web_contents_->OnInterfaceRequestFromFrame(
+      main_frame, mojom::TestAdder::Name_, &adder_receiver_pipe);
+  mojo::Remote<mojom::TestDoubler> doubler;
+  mojo::ScopedMessagePipeHandle doubler_receiver_pipe =
+      doubler.BindNewPipeAndPassReceiver().PassPipe();
+  cast_web_contents_->OnInterfaceRequestFromFrame(
+      main_frame, mojom::TestDoubler::Name_, &doubler_receiver_pipe);
+
+  base::RunLoop add_loop;
+  adder->Add(37, 5, base::BindLambdaForTesting([&](int32_t result) {
+               EXPECT_EQ(42, result);
+               add_loop.Quit();
+             }));
+  add_loop.Run();
+
+  base::RunLoop double_loop;
+  doubler->Double(21, base::BindLambdaForTesting([&](int32_t result) {
+                    EXPECT_EQ(42, result);
+                    double_loop.Quit();
+                  }));
+  double_loop.Run();
+
+  EXPECT_EQ(1u, provider.num_adders());
+  EXPECT_EQ(1u, provider.num_doublers());
+
+  // Now verify that the same interfaces are also reachable at the same binders
+  // when going through the newer BrowserInterfaceBroker path. For simplicity
+  // the test JS here does not have access to bindings and so does not make
+  // calls on the interfaces. It is however totally sufficient for us to verify
+  // that the page's requests result in new receivers being bound inside
+  // TestInterfaceProvider.
+  base::RunLoop loop;
+  provider.WaitForRequests(2, loop.QuitClosure());
+  embedded_test_server()->ServeFilesFromSourceDirectory(GetTestDataPath());
+  StartTestServer();
+  const GURL kUrl{embedded_test_server()->GetURL("/interface_binding.html")};
+  cast_web_contents_->LoadUrl(kUrl);
+  loop.Run();
+
+  EXPECT_EQ(2u, provider.num_adders());
+  EXPECT_EQ(2u, provider.num_doublers());
+}
+
 }  // namespace chromecast
 
 #endif  // CHROMECAST_BROWSER_CAST_WEB_CONTENTS_BROWSERTEST_H_
diff --git a/chromecast/browser/cast_web_contents_impl.cc b/chromecast/browser/cast_web_contents_impl.cc
index c962ebf..f327e2ec 100644
--- a/chromecast/browser/cast_web_contents_impl.cc
+++ b/chromecast/browser/cast_web_contents_impl.cc
@@ -397,6 +397,31 @@
   return &binder_registry_;
 }
 
+bool CastWebContentsImpl::TryBindReceiver(
+    mojo::GenericPendingReceiver& receiver) {
+  const std::string interface_name = *receiver.interface_name();
+  mojo::ScopedMessagePipeHandle interface_pipe = receiver.PassPipe();
+  if (binder_registry_.TryBindInterface(interface_name, &interface_pipe)) {
+    return true;
+  }
+
+  for (auto& entry : interface_providers_map_) {
+    auto const& interface_set = entry.first;
+    // Interface is provided by this InterfaceProvider.
+    if (interface_set.find(interface_name) != interface_set.end()) {
+      auto* interface_provider = entry.second;
+      interface_provider->GetInterfaceByName(interface_name,
+                                             std::move(interface_pipe));
+      return true;
+    }
+  }
+
+  // Unsuccessful, so give the caller its receiver back.
+  receiver =
+      mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe));
+  return false;
+}
+
 void CastWebContentsImpl::RegisterInterfaceProvider(
     const InterfaceSet& interface_set,
     service_manager::InterfaceProvider* interface_provider) {
@@ -504,18 +529,12 @@
   if (!can_bind_interfaces()) {
     return;
   }
-  if (binder_registry_.TryBindInterface(interface_name, interface_pipe)) {
-    return;
-  }
-  for (auto& entry : interface_providers_map_) {
-    auto const& interface_set = entry.first;
-    // Interface is provided by this InterfaceProvider.
-    if (interface_set.find(interface_name) != interface_set.end()) {
-      auto* interface_provider = entry.second;
-      interface_provider->GetInterfaceByName(interface_name,
-                                             std::move(*interface_pipe));
-      break;
-    }
+
+  mojo::GenericPendingReceiver receiver(interface_name,
+                                        std::move(*interface_pipe));
+  if (!TryBindReceiver(receiver)) {
+    // If binding was unsuccessful, give the caller its pipe back.
+    *interface_pipe = receiver.PassPipe();
   }
 }
 
diff --git a/chromecast/browser/cast_web_contents_impl.h b/chromecast/browser/cast_web_contents_impl.h
index 535c99df..9a31976 100644
--- a/chromecast/browser/cast_web_contents_impl.h
+++ b/chromecast/browser/cast_web_contents_impl.h
@@ -63,6 +63,7 @@
       const InterfaceSet& interface_set,
       service_manager::InterfaceProvider* interface_provider) override;
   service_manager::BinderRegistry* binder_registry() override;
+  bool TryBindReceiver(mojo::GenericPendingReceiver& receiver) override;
   void BlockMediaLoading(bool blocked) override;
   void BlockMediaStarting(bool blocked) override;
   void EnableBackgroundVideoPlayback(bool enabled) override;
diff --git a/chromecast/browser/test/data/interface_binding.html b/chromecast/browser/test/data/interface_binding.html
new file mode 100644
index 0000000..70d2c48
--- /dev/null
+++ b/chromecast/browser/test/data/interface_binding.html
@@ -0,0 +1,17 @@
+<html>
+    <head><title>Binding some Mojo interfaces</title></head>
+    <body>
+        <script>
+          function bindInterface(name) {
+            const {handle0, handle1} = Mojo.createMessagePipe();
+            Mojo.bindInterface(name, handle0);
+          }
+
+          // The browser test which uses this page will succeed once these
+          // bindInterface requests reach browser-side binders registered by
+          // the test.
+          bindInterface('chromecast.mojom.TestAdder');
+          bindInterface('chromecast.mojom.TestDoubler');
+        </script>
+    </body>
+</html>
diff --git a/chromecast/browser/test/mock_cast_web_view.cc b/chromecast/browser/test/mock_cast_web_view.cc
index 6af9c9d8..fa9bf9b 100644
--- a/chromecast/browser/test/mock_cast_web_view.cc
+++ b/chromecast/browser/test/mock_cast_web_view.cc
@@ -13,6 +13,10 @@
   return &registry_;
 }
 
+bool MockCastWebContents::TryBindReceiver(mojo::GenericPendingReceiver&) {
+  return false;
+}
+
 MockCastWebView::MockCastWebView() {
   mock_cast_web_contents_ = std::make_unique<MockCastWebContents>();
 }
diff --git a/chromecast/browser/test/mock_cast_web_view.h b/chromecast/browser/test/mock_cast_web_view.h
index 22c366c..5393bb5 100644
--- a/chromecast/browser/test/mock_cast_web_view.h
+++ b/chromecast/browser/test/mock_cast_web_view.h
@@ -64,6 +64,7 @@
   MOCK_METHOD(bool, can_bind_interfaces, (), (override));
 
   service_manager::BinderRegistry* binder_registry() override;
+  bool TryBindReceiver(mojo::GenericPendingReceiver&) override;
 
  private:
   service_manager::BinderRegistry registry_;
diff --git a/chromecast/browser/test_interfaces.test-mojom b/chromecast/browser/test_interfaces.test-mojom
new file mode 100644
index 0000000..f5ca2a7
--- /dev/null
+++ b/chromecast/browser/test_interfaces.test-mojom
@@ -0,0 +1,13 @@
+// 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.
+
+module chromecast.mojom;
+
+interface TestAdder {
+  Add(int32 a, int32 b) => (int32 result);
+};
+
+interface TestDoubler {
+  Double(int32 x) => (int32 result);
+};
diff --git a/chromeos/services/cellular_setup/BUILD.gn b/chromeos/services/cellular_setup/BUILD.gn
index e6616ba..f8283f7 100644
--- a/chromeos/services/cellular_setup/BUILD.gn
+++ b/chromeos/services/cellular_setup/BUILD.gn
@@ -72,6 +72,8 @@
   testonly = true
 
   sources = [
+    "esim_test_utils.cc",
+    "esim_test_utils.h",
     "fake_ota_activator.cc",
     "fake_ota_activator.h",
   ]
@@ -90,6 +92,10 @@
     "cellular_setup_impl_unittest.cc",
     "cellular_setup_service_unittest.cc",
     "esim_manager_unittest.cc",
+    "esim_profile_unittest.cc",
+    "esim_test_base.cc",
+    "esim_test_base.h",
+    "euicc_unittest.cc",
     "ota_activator_impl_unittest.cc",
   ]
 
diff --git a/chromeos/services/cellular_setup/esim_manager_unittest.cc b/chromeos/services/cellular_setup/esim_manager_unittest.cc
index 2bcbb223..3ccf691 100644
--- a/chromeos/services/cellular_setup/esim_manager_unittest.cc
+++ b/chromeos/services/cellular_setup/esim_manager_unittest.cc
@@ -3,196 +3,21 @@
 // found in the LICENSE file.
 
 #include "chromeos/services/cellular_setup/esim_manager.h"
-#include "base/bind.h"
-#include "base/callback_forward.h"
-#include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/test/task_environment.h"
 #include "chromeos/dbus/hermes/hermes_clients.h"
 #include "chromeos/dbus/hermes/hermes_euicc_client.h"
-#include "chromeos/dbus/hermes/hermes_manager_client.h"
-#include "chromeos/dbus/shill/shill_clients.h"
-#include "chromeos/dbus/shill/shill_manager_client.h"
-#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom-forward.h"
+#include "chromeos/services/cellular_setup/esim_test_base.h"
+#include "chromeos/services/cellular_setup/esim_test_utils.h"
 #include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom.h"
-#include "dbus/object_path.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/hermes/dbus-constants.h"
 
 namespace chromeos {
 namespace cellular_setup {
 
-namespace {
-const char* kTestEuiccPath = "/org/chromium/Hermes/Euicc/0";
-const char* kTestEid = "12345678901234567890123456789012";
-
-}  // namespace
-
-// Fake observer for testing ESimManager.
-class ESimManagerTestObserver : public mojom::ESimManagerObserver {
+class ESimManagerTest : public ESimTestBase {
  public:
-  ESimManagerTestObserver() = default;
-  ESimManagerTestObserver(const ESimManagerTestObserver&) = delete;
-  ESimManagerTestObserver& operator=(const ESimManagerTestObserver&) = delete;
-  ~ESimManagerTestObserver() override = default;
-
-  // mojom::ESimManagerObserver:
-  void OnAvailableEuiccListChanged() override {
-    available_euicc_list_change_count_++;
-  }
-  void OnProfileListChanged(mojo::PendingRemote<mojom::Euicc> euicc) override {
-    profile_list_change_calls_.push_back(std::move(euicc));
-  }
-  void OnEuiccChanged(mojo::PendingRemote<mojom::Euicc> euicc) override {
-    euicc_change_calls_.push_back(std::move(euicc));
-  }
-  void OnProfileChanged(
-      mojo::PendingRemote<mojom::ESimProfile> esim_profile) override {
-    profile_change_calls_.push_back(std::move(esim_profile));
-  }
-
-  mojo::PendingRemote<mojom::ESimManagerObserver> GenerateRemote() {
-    return receiver_.BindNewPipeAndPassRemote();
-  }
-
-  void Reset() {
-    available_euicc_list_change_count_ = 0;
-    profile_list_change_calls_.clear();
-    euicc_change_calls_.clear();
-    profile_change_calls_.clear();
-  }
-
-  mojo::PendingRemote<mojom::Euicc> PopLastChangedEuicc() {
-    mojo::PendingRemote<mojom::Euicc> euicc =
-        std::move(euicc_change_calls_.front());
-    euicc_change_calls_.erase(euicc_change_calls_.begin());
-    return euicc;
-  }
-
-  mojo::PendingRemote<mojom::ESimProfile> PopLastChangedESimProfile() {
-    mojo::PendingRemote<mojom::ESimProfile> esim_profile =
-        std::move(profile_change_calls_.front());
-    profile_change_calls_.erase(profile_change_calls_.begin());
-    return esim_profile;
-  }
-
-  int available_euicc_list_change_count() {
-    return available_euicc_list_change_count_;
-  }
-
-  const std::vector<mojo::PendingRemote<mojom::Euicc>>&
-  profile_list_change_calls() {
-    return profile_list_change_calls_;
-  }
-
-  const std::vector<mojo::PendingRemote<mojom::Euicc>>& euicc_change_calls() {
-    return euicc_change_calls_;
-  }
-
-  const std::vector<mojo::PendingRemote<mojom::ESimProfile>>&
-  profile_change_calls() {
-    return profile_change_calls_;
-  }
-
- private:
-  int available_euicc_list_change_count_ = 0;
-  std::vector<mojo::PendingRemote<mojom::Euicc>> profile_list_change_calls_;
-  std::vector<mojo::PendingRemote<mojom::Euicc>> euicc_change_calls_;
-  std::vector<mojo::PendingRemote<mojom::ESimProfile>> profile_change_calls_;
-  mojo::Receiver<mojom::ESimManagerObserver> receiver_{this};
-};
-
-class ESimManagerTest : public testing::Test {
- public:
-  using InstallResultPair =
-      std::pair<mojom::ProfileInstallResult, mojom::ESimProfilePtr>;
-
-  ESimManagerTest() {
-    if (!ShillManagerClient::Get())
-      shill_clients::InitializeFakes();
-    if (!HermesManagerClient::Get())
-      hermes_clients::InitializeFakes();
-  }
+  ESimManagerTest() = default;
   ESimManagerTest(const ESimManagerTest&) = delete;
   ESimManagerTest& operator=(const ESimManagerTest&) = delete;
-  ~ESimManagerTest() override = default;
-
-  void SetUp() override {
-    HermesManagerClient::Get()->GetTestInterface()->ClearEuiccs();
-    HermesEuiccClient::Get()->GetTestInterface()->SetInteractiveDelay(
-        base::TimeDelta::FromSeconds(0));
-    esim_manager_ = std::make_unique<ESimManager>();
-    observer_ = std::make_unique<ESimManagerTestObserver>();
-    esim_manager_->AddObserver(observer_->GenerateRemote());
-  }
-
-  void TearDown() override {
-    esim_manager_.reset();
-    observer_.reset();
-    HermesEuiccClient::Get()->GetTestInterface()->ResetPendingEventsRequested();
-  }
-
-  void SetupEuicc() {
-    HermesManagerClient::Get()->GetTestInterface()->AddEuicc(
-        dbus::ObjectPath(kTestEuiccPath), kTestEid, true);
-    base::RunLoop().RunUntilIdle();
-  }
-
-  std::vector<mojo::PendingRemote<mojom::Euicc>> GetAvailableEuiccs() {
-    std::vector<mojo::PendingRemote<mojom::Euicc>> result;
-    base::RunLoop run_loop;
-    esim_manager_->GetAvailableEuiccs(base::BindOnce(
-        [](std::vector<mojo::PendingRemote<mojom::Euicc>>* result,
-           base::OnceClosure quit_closure,
-           std::vector<mojo::PendingRemote<mojom::Euicc>> available_euiccs) {
-          for (auto& euicc : available_euiccs)
-            result->push_back(std::move(euicc));
-          std::move(quit_closure).Run();
-        },
-        &result, run_loop.QuitClosure()));
-    run_loop.Run();
-    return result;
-  }
-
-  mojom::EuiccPropertiesPtr GetEuiccProperties(
-      const mojo::Remote<mojom::Euicc>& euicc) {
-    mojom::EuiccPropertiesPtr result;
-    base::RunLoop run_loop;
-    euicc->GetProperties(base::BindOnce(
-        [](mojom::EuiccPropertiesPtr* out, base::OnceClosure quit_closure,
-           mojom::EuiccPropertiesPtr properties) {
-          *out = std::move(properties);
-          std::move(quit_closure).Run();
-        },
-        &result, run_loop.QuitClosure()));
-    run_loop.Run();
-    return result;
-  }
-
-  mojom::ESimProfilePropertiesPtr GetESimProfileProperties(
-      const mojo::Remote<mojom::ESimProfile>& esim_profile) {
-    mojom::ESimProfilePropertiesPtr result;
-    base::RunLoop run_loop;
-    esim_profile->GetProperties(base::BindOnce(
-        [](mojom::ESimProfilePropertiesPtr* out, base::OnceClosure quit_closure,
-           mojom::ESimProfilePropertiesPtr properties) {
-          *out = std::move(properties);
-          std::move(quit_closure).Run();
-        },
-        &result, run_loop.QuitClosure()));
-    run_loop.Run();
-    return result;
-  }
-
-  ESimManager* esim_manager() { return esim_manager_.get(); }
-  ESimManagerTestObserver* observer() { return observer_.get(); }
-
- private:
-  base::test::SingleThreadTaskEnvironment task_environment_;
-  std::unique_ptr<ESimManager> esim_manager_;
-  std::unique_ptr<ESimManagerTestObserver> observer_;
 };
 
 TEST_F(ESimManagerTest, GetAvailableEuiccs) {
diff --git a/chromeos/services/cellular_setup/esim_profile.cc b/chromeos/services/cellular_setup/esim_profile.cc
index 353959ca..9228f80 100644
--- a/chromeos/services/cellular_setup/esim_profile.cc
+++ b/chromeos/services/cellular_setup/esim_profile.cc
@@ -66,8 +66,10 @@
 }
 
 void ESimProfile::EnableProfile(EnableProfileCallback callback) {
-  if (properties_->state == mojom::ProfileState::kActive) {
-    NET_LOG(ERROR) << "Profile enable failed: Profile already enabled";
+  if (properties_->state == mojom::ProfileState::kActive ||
+      properties_->state == mojom::ProfileState::kPending) {
+    NET_LOG(ERROR)
+        << "Profile enable failed: Profile already enabled or not installed";
     std::move(callback).Run(mojom::ESimOperationResult::kFailure);
     return;
   }
@@ -79,8 +81,10 @@
 }
 
 void ESimProfile::DisableProfile(DisableProfileCallback callback) {
-  if (properties_->state == mojom::ProfileState::kInactive) {
-    NET_LOG(ERROR) << "Profile enable failed: Profile already disabled";
+  if (properties_->state == mojom::ProfileState::kInactive ||
+      properties_->state == mojom::ProfileState::kPending) {
+    NET_LOG(ERROR)
+        << "Profile enable failed: Profile already disabled or not installed";
     std::move(callback).Run(mojom::ESimOperationResult::kFailure);
     return;
   }
diff --git a/chromeos/services/cellular_setup/esim_profile_unittest.cc b/chromeos/services/cellular_setup/esim_profile_unittest.cc
new file mode 100644
index 0000000..ce886c6
--- /dev/null
+++ b/chromeos/services/cellular_setup/esim_profile_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/dbus/hermes/hermes_euicc_client.h"
+#include "chromeos/dbus/hermes/hermes_profile_client.h"
+#include "chromeos/services/cellular_setup/esim_test_base.h"
+#include "chromeos/services/cellular_setup/esim_test_utils.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+namespace {
+
+mojom::ProfileInstallResult InstallProfile(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile,
+    const std::string& confirmation_code) {
+  mojom::ProfileInstallResult install_result;
+
+  base::RunLoop run_loop;
+  esim_profile->InstallProfile(
+      confirmation_code, base::BindOnce(
+                             [](mojom::ProfileInstallResult* out_install_result,
+                                base::OnceClosure quit_closure,
+                                mojom::ProfileInstallResult install_result) {
+                               *out_install_result = install_result;
+                               std::move(quit_closure).Run();
+                             },
+                             &install_result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return install_result;
+}
+
+mojom::ESimOperationResult UninstallProfile(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile) {
+  mojom::ESimOperationResult uninstall_result;
+
+  base::RunLoop run_loop;
+  esim_profile->UninstallProfile(base::BindOnce(
+      [](mojom::ESimOperationResult* out_uninstall_result,
+         base::OnceClosure quit_closure,
+         mojom::ESimOperationResult uninstall_result) {
+        *out_uninstall_result = uninstall_result;
+        std::move(quit_closure).Run();
+      },
+      &uninstall_result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return uninstall_result;
+}
+
+mojom::ESimOperationResult EnableProfile(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile) {
+  mojom::ESimOperationResult enable_result;
+
+  base::RunLoop run_loop;
+  esim_profile->EnableProfile(base::BindOnce(
+      [](mojom::ESimOperationResult* out_enable_result,
+         base::OnceClosure quit_closure,
+         mojom::ESimOperationResult enable_result) {
+        *out_enable_result = enable_result;
+        std::move(quit_closure).Run();
+      },
+      &enable_result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return enable_result;
+}
+
+mojom::ESimOperationResult DisableProfile(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile) {
+  mojom::ESimOperationResult disable_result;
+
+  base::RunLoop run_loop;
+  esim_profile->DisableProfile(base::BindOnce(
+      [](mojom::ESimOperationResult* out_disable_result,
+         base::OnceClosure quit_closure,
+         mojom::ESimOperationResult disable_result) {
+        *out_disable_result = disable_result;
+        std::move(quit_closure).Run();
+      },
+      &disable_result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return disable_result;
+}
+
+mojom::ESimOperationResult SetProfileNickname(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile,
+    const base::string16& nickname) {
+  mojom::ESimOperationResult result;
+
+  base::RunLoop run_loop;
+  esim_profile->SetProfileNickname(
+      nickname, base::BindOnce(
+                    [](mojom::ESimOperationResult* out_result,
+                       base::OnceClosure quit_closure,
+                       mojom::ESimOperationResult result) {
+                      *out_result = result;
+                      std::move(quit_closure).Run();
+                    },
+                    &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+}  // namespace
+
+class ESimProfileTest : public ESimTestBase {
+ public:
+  ESimProfileTest() = default;
+  ESimProfileTest(const ESimProfileTest&) = delete;
+  ESimProfileTest& operator=(const ESimProfileTest&) = delete;
+
+  void SetUp() override {
+    ESimTestBase::SetUp();
+    SetupEuicc();
+  }
+
+  mojo::Remote<mojom::ESimProfile> GetESimProfileForIccid(
+      const std::string& eid,
+      const std::string& iccid) {
+    mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(eid);
+    if (!euicc.is_bound()) {
+      return mojo::Remote<mojom::ESimProfile>();
+    }
+    std::vector<mojo::PendingRemote<mojom::ESimProfile>>
+        profile_pending_remotes = GetProfileList(euicc);
+    for (auto& profile_pending_remote : profile_pending_remotes) {
+      mojo::Remote<mojom::ESimProfile> esim_profile(
+          std::move(profile_pending_remote));
+      mojom::ESimProfilePropertiesPtr profile_properties =
+          GetESimProfileProperties(esim_profile);
+      if (profile_properties->iccid == iccid) {
+        return esim_profile;
+      }
+    }
+    return mojo::Remote<mojom::ESimProfile>();
+  }
+};
+
+TEST_F(ESimProfileTest, GetProperties) {
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
+      hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  HermesProfileClient::Properties* dbus_properties =
+      HermesProfileClient::Get()->GetProperties(profile_path);
+
+  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
+      ESimTestBase::kTestEid, dbus_properties->iccid().value());
+  ASSERT_TRUE(esim_profile.is_bound());
+  mojom::ESimProfilePropertiesPtr mojo_properties =
+      GetESimProfileProperties(esim_profile);
+  EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid);
+}
+
+TEST_F(ESimProfileTest, InstallProfile) {
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(ESimTestBase::kTestEuiccPath),
+      hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  HermesProfileClient::Properties* dbus_properties =
+      HermesProfileClient::Get()->GetProperties(profile_path);
+
+  // Verify that install errors return error code properly.
+  euicc_test->QueueHermesErrorStatus(
+      HermesResponseStatus::kErrorNeedConfirmationCode);
+  mojo::Remote<mojom::ESimProfile> esim_profile = GetESimProfileForIccid(
+      ESimTestBase::kTestEid, dbus_properties->iccid().value());
+  ASSERT_TRUE(esim_profile.is_bound());
+  mojom::ProfileInstallResult install_result = InstallProfile(esim_profile, "");
+  EXPECT_EQ(mojom::ProfileInstallResult::kErrorNeedsConfirmationCode,
+            install_result);
+
+  // Verify that installing pending profile returns proper results
+  // and updates esim_profile properties.
+  install_result = InstallProfile(esim_profile, "");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, install_result);
+  mojom::ESimProfilePropertiesPtr mojo_properties =
+      GetESimProfileProperties(esim_profile);
+  EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid);
+  EXPECT_NE(mojo_properties->state, mojom::ProfileState::kPending);
+  EXPECT_EQ(3u, observer()->profile_list_change_calls().size());
+}
+
+TEST_F(ESimProfileTest, UninstallProfile) {
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "");
+  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, observer()->profile_list_change_calls().size());
+  observer()->Reset();
+  HermesProfileClient::Properties* pending_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(pending_profile_path);
+  HermesProfileClient::Properties* active_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(active_profile_path);
+
+  // Verify that uninstall error codes are returned properly.
+  euicc_test->QueueHermesErrorStatus(
+      HermesResponseStatus::kErrorInvalidResponse);
+  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
+      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(active_esim_profile.is_bound());
+  mojom::ESimOperationResult result = UninstallProfile(active_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
+  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
+
+  // Verify that pending profiles cannot be uninstalled
+  observer()->Reset();
+  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
+      GetESimProfileForIccid(ESimTestBase::kTestEid,
+                             pending_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(pending_esim_profile.is_bound());
+  result = UninstallProfile(pending_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
+  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
+
+  // Verify that uninstall removes the profile and notifies observers properly.
+  observer()->Reset();
+  result = UninstallProfile(active_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);
+  ASSERT_EQ(1u, observer()->profile_list_change_calls().size());
+  EXPECT_EQ(1u, GetProfileList(GetEuiccForEid(ESimTestBase::kTestEid)).size());
+}
+
+TEST_F(ESimProfileTest, EnableProfile) {
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath inactive_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kInactive, "");
+  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, observer()->profile_list_change_calls().size());
+  observer()->Reset();
+  HermesProfileClient::Properties* pending_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(pending_profile_path);
+  HermesProfileClient::Properties* inactive_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(inactive_profile_path);
+
+  // Verify that pending profiles cannot be enabled.
+  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
+      GetESimProfileForIccid(ESimTestBase::kTestEid,
+                             pending_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(pending_esim_profile.is_bound());
+  mojom::ESimOperationResult result = EnableProfile(pending_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
+  EXPECT_EQ(0u, observer()->profile_change_calls().size());
+
+  // Verify that enabling profile returns result properly.
+  mojo::Remote<mojom::ESimProfile> inactive_esim_profile =
+      GetESimProfileForIccid(ESimTestBase::kTestEid,
+                             inactive_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(inactive_esim_profile.is_bound());
+  result = EnableProfile(inactive_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);
+
+  mojom::ESimProfilePropertiesPtr inactive_profile_mojo_properties =
+      GetESimProfileProperties(inactive_esim_profile);
+  EXPECT_EQ(inactive_profile_dbus_properties->iccid().value(),
+            inactive_profile_mojo_properties->iccid);
+  EXPECT_EQ(mojom::ProfileState::kActive,
+            inactive_profile_mojo_properties->state);
+}
+
+TEST_F(ESimProfileTest, DisableProfile) {
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "");
+  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, observer()->profile_list_change_calls().size());
+  observer()->Reset();
+  HermesProfileClient::Properties* pending_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(pending_profile_path);
+  HermesProfileClient::Properties* active_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(active_profile_path);
+
+  // Verify that pending profiles cannot be disabled.
+  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
+      GetESimProfileForIccid(ESimTestBase::kTestEid,
+                             pending_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(pending_esim_profile.is_bound());
+  mojom::ESimOperationResult result = DisableProfile(pending_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
+  EXPECT_EQ(0u, observer()->profile_change_calls().size());
+
+  // Verify that disabling profile returns result properly.
+  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
+      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(active_esim_profile.is_bound());
+  result = DisableProfile(active_esim_profile);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);
+
+  mojom::ESimProfilePropertiesPtr active_profile_mojo_properties =
+      GetESimProfileProperties(active_esim_profile);
+  EXPECT_EQ(active_profile_dbus_properties->iccid().value(),
+            active_profile_mojo_properties->iccid);
+  EXPECT_EQ(mojom::ProfileState::kInactive,
+            active_profile_mojo_properties->state);
+}
+
+TEST_F(ESimProfileTest, SetProfileNickName) {
+  const base::string16 test_nickname = base::UTF8ToUTF16("Test nickname");
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "");
+  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, observer()->profile_list_change_calls().size());
+  observer()->Reset();
+  HermesProfileClient::Properties* pending_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(pending_profile_path);
+  HermesProfileClient::Properties* active_profile_dbus_properties =
+      HermesProfileClient::Get()->GetProperties(active_profile_path);
+
+  // Verify that pending profiles cannot be modified.
+  mojo::Remote<mojom::ESimProfile> pending_esim_profile =
+      GetESimProfileForIccid(ESimTestBase::kTestEid,
+                             pending_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(pending_esim_profile.is_bound());
+  mojom::ESimOperationResult result =
+      SetProfileNickname(pending_esim_profile, test_nickname);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure, result);
+  EXPECT_EQ(0u, observer()->profile_change_calls().size());
+
+  // Verify that nickname can be set on active profiles.
+  mojo::Remote<mojom::ESimProfile> active_esim_profile = GetESimProfileForIccid(
+      ESimTestBase::kTestEid, active_profile_dbus_properties->iccid().value());
+  ASSERT_TRUE(active_esim_profile.is_bound());
+  result = SetProfileNickname(active_esim_profile, test_nickname);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(mojom::ESimOperationResult::kSuccess, result);
+
+  mojom::ESimProfilePropertiesPtr active_profile_mojo_properties =
+      GetESimProfileProperties(active_esim_profile);
+  EXPECT_EQ(test_nickname, active_profile_mojo_properties->nickname);
+}
+
+}  // namespace cellular_setup
+}  // namespace chromeos
diff --git a/chromeos/services/cellular_setup/esim_test_base.cc b/chromeos/services/cellular_setup/esim_test_base.cc
new file mode 100644
index 0000000..b2edfde
--- /dev/null
+++ b/chromeos/services/cellular_setup/esim_test_base.cc
@@ -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.
+
+#include "chromeos/services/cellular_setup/esim_test_base.h"
+
+#include <memory.h>
+
+#include "chromeos/dbus/hermes/hermes_clients.h"
+#include "chromeos/dbus/hermes/hermes_euicc_client.h"
+#include "chromeos/dbus/hermes/hermes_manager_client.h"
+#include "chromeos/dbus/shill/shill_clients.h"
+#include "chromeos/dbus/shill/shill_manager_client.h"
+#include "chromeos/services/cellular_setup/esim_manager.h"
+#include "chromeos/services/cellular_setup/esim_test_utils.h"
+#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom-forward.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+const char* ESimTestBase::kTestEuiccPath = "/org/chromium/Hermes/Euicc/0";
+const char* ESimTestBase::kTestEid = "12345678901234567890123456789012";
+
+ESimTestBase::ESimTestBase() {
+  if (!ShillManagerClient::Get())
+    shill_clients::InitializeFakes();
+  if (!HermesManagerClient::Get())
+    hermes_clients::InitializeFakes();
+}
+
+ESimTestBase::~ESimTestBase() = default;
+
+void ESimTestBase::SetUp() {
+  HermesManagerClient::Get()->GetTestInterface()->ClearEuiccs();
+  HermesEuiccClient::Get()->GetTestInterface()->SetInteractiveDelay(
+      base::TimeDelta::FromSeconds(0));
+  esim_manager_ = std::make_unique<ESimManager>();
+  observer_ = std::make_unique<ESimManagerTestObserver>();
+  esim_manager_->AddObserver(observer_->GenerateRemote());
+}
+
+void ESimTestBase::TearDown() {
+  esim_manager_.reset();
+  observer_.reset();
+  HermesEuiccClient::Get()->GetTestInterface()->ResetPendingEventsRequested();
+}
+
+void ESimTestBase::SetupEuicc() {
+  HermesManagerClient::Get()->GetTestInterface()->AddEuicc(
+      dbus::ObjectPath(kTestEuiccPath), kTestEid, true);
+  base::RunLoop().RunUntilIdle();
+}
+
+std::vector<mojo::PendingRemote<mojom::Euicc>>
+ESimTestBase::GetAvailableEuiccs() {
+  std::vector<mojo::PendingRemote<mojom::Euicc>> result;
+  base::RunLoop run_loop;
+  esim_manager()->GetAvailableEuiccs(base::BindOnce(
+      [](std::vector<mojo::PendingRemote<mojom::Euicc>>* result,
+         base::OnceClosure quit_closure,
+         std::vector<mojo::PendingRemote<mojom::Euicc>> available_euiccs) {
+        for (auto& euicc : available_euiccs)
+          result->push_back(std::move(euicc));
+        std::move(quit_closure).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+mojo::Remote<mojom::Euicc> ESimTestBase::GetEuiccForEid(
+    const std::string& eid) {
+  std::vector<mojo::PendingRemote<mojom::Euicc>> euicc_pending_remotes =
+      GetAvailableEuiccs();
+  for (auto& euicc_pending_remote : euicc_pending_remotes) {
+    mojo::Remote<mojom::Euicc> euicc(std::move(euicc_pending_remote));
+    mojom::EuiccPropertiesPtr euicc_properties = GetEuiccProperties(euicc);
+    if (euicc_properties->eid == eid) {
+      return euicc;
+    }
+  }
+  return mojo::Remote<mojom::Euicc>();
+}
+
+}  // namespace cellular_setup
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/esim_test_base.h b/chromeos/services/cellular_setup/esim_test_base.h
new file mode 100644
index 0000000..8f3c6ff
--- /dev/null
+++ b/chromeos/services/cellular_setup/esim_test_base.h
@@ -0,0 +1,60 @@
+// 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 CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_BASE_H_
+#define CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_BASE_H_
+
+#include "base/test/task_environment.h"
+#include "chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.h"
+#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace test {
+class SingleThreadTaskEnvironment;
+}  // namespace test
+}  // namespace base
+
+namespace chromeos {
+namespace cellular_setup {
+
+class ESimManager;
+
+// Base class for testing eSIM mojo impl classes.
+class ESimTestBase : public testing::Test {
+ public:
+  static const char* kTestEuiccPath;
+  static const char* kTestEid;
+
+  // testing::Test:
+  void SetUp() override;
+  void TearDown() override;
+
+  // Creates a test euicc.
+  void SetupEuicc();
+
+  // Returns list of available euiccs under the test ESimManager.
+  std::vector<mojo::PendingRemote<mojom::Euicc>> GetAvailableEuiccs();
+
+  // Returns euicc with given |eid| under the test ESimManager.
+  mojo::Remote<mojom::Euicc> GetEuiccForEid(const std::string& eid);
+
+ protected:
+  ESimTestBase();
+  ~ESimTestBase() override;
+
+  ESimManager* esim_manager() { return esim_manager_.get(); }
+  ESimManagerTestObserver* observer() { return observer_.get(); }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<ESimManager> esim_manager_;
+  std::unique_ptr<ESimManagerTestObserver> observer_;
+};
+
+}  // namespace cellular_setup
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_BASE_H_
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/esim_test_utils.cc b/chromeos/services/cellular_setup/esim_test_utils.cc
new file mode 100644
index 0000000..f886d05
--- /dev/null
+++ b/chromeos/services/cellular_setup/esim_test_utils.cc
@@ -0,0 +1,60 @@
+// 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 "chromeos/services/cellular_setup/esim_test_utils.h"
+
+#include "base/run_loop.h"
+#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+mojom::EuiccPropertiesPtr GetEuiccProperties(
+    const mojo::Remote<mojom::Euicc>& euicc) {
+  mojom::EuiccPropertiesPtr result;
+  base::RunLoop run_loop;
+  euicc->GetProperties(base::BindOnce(
+      [](mojom::EuiccPropertiesPtr* out, base::OnceClosure quit_closure,
+         mojom::EuiccPropertiesPtr properties) {
+        *out = std::move(properties);
+        std::move(quit_closure).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+mojom::ESimProfilePropertiesPtr GetESimProfileProperties(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile) {
+  mojom::ESimProfilePropertiesPtr result;
+  base::RunLoop run_loop;
+  esim_profile->GetProperties(base::BindOnce(
+      [](mojom::ESimProfilePropertiesPtr* out, base::OnceClosure quit_closure,
+         mojom::ESimProfilePropertiesPtr properties) {
+        *out = std::move(properties);
+        std::move(quit_closure).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+std::vector<mojo::PendingRemote<mojom::ESimProfile>> GetProfileList(
+    const mojo::Remote<mojom::Euicc>& euicc) {
+  std::vector<mojo::PendingRemote<mojom::ESimProfile>> result;
+  base::RunLoop run_loop;
+  euicc->GetProfileList(base::BindOnce(
+      [](std::vector<mojo::PendingRemote<mojom::ESimProfile>>* out,
+         base::OnceClosure quit_closure,
+         std::vector<mojo::PendingRemote<mojom::ESimProfile>> profile_list) {
+        *out = std::move(profile_list);
+        std::move(quit_closure).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+}  // namespace cellular_setup
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/esim_test_utils.h b/chromeos/services/cellular_setup/esim_test_utils.h
new file mode 100644
index 0000000..38ec7c8
--- /dev/null
+++ b/chromeos/services/cellular_setup/esim_test_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_UTILS_H_
+#define CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_UTILS_H_
+
+#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom-forward.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+// Calls GetProperties on a remote euicc object and waits for
+// result. Returns the resulting EuiccProperties structure.
+mojom::EuiccPropertiesPtr GetEuiccProperties(
+    const mojo::Remote<mojom::Euicc>& euicc);
+
+// Calls GetProperties on a remote esim_profile object and waits
+// for result. Returns the resulting EuiccProperties structure.
+mojom::ESimProfilePropertiesPtr GetESimProfileProperties(
+    const mojo::Remote<mojom::ESimProfile>& esim_profile);
+
+// Calls GetProfileList on a remote euicc object and waits
+// for result. Returns the resulting list of ESimProfile
+// pending remotes.
+std::vector<mojo::PendingRemote<mojom::ESimProfile>> GetProfileList(
+    const mojo::Remote<mojom::Euicc>& euicc);
+
+}  // namespace cellular_setup
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_CELLULAR_SETUP_ESIM_TEST_UTILS_H_
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/euicc.cc b/chromeos/services/cellular_setup/euicc.cc
index f0cb077..52d7da38 100644
--- a/chromeos/services/cellular_setup/euicc.cc
+++ b/chromeos/services/cellular_setup/euicc.cc
@@ -41,12 +41,12 @@
     const std::string& activation_code,
     const std::string& confirmation_code,
     InstallProfileFromActivationCodeCallback callback) {
-  ESimProfile* profile_info =
-      GetPendingProfileInfoFromActivationCode(activation_code);
-  if (!profile_info) {
+  ESimProfile* profile_info = nullptr;
+  mojom::ProfileInstallResult status =
+      GetPendingProfileInfoFromActivationCode(activation_code, &profile_info);
+  if (profile_info && status != mojom::ProfileInstallResult::kSuccess) {
     // Return early if profile was found but not in the correct state.
-    std::move(callback).Run(mojom::ProfileInstallResult::kFailure,
-                            mojo::NullRemote());
+    std::move(callback).Run(status, mojo::NullRemote());
     return;
   }
 
@@ -144,8 +144,9 @@
                               : mojom::ESimOperationResult::kFailure);
 }
 
-ESimProfile* Euicc::GetPendingProfileInfoFromActivationCode(
-    const std::string& activation_code) {
+mojom::ProfileInstallResult Euicc::GetPendingProfileInfoFromActivationCode(
+    const std::string& activation_code,
+    ESimProfile** profile_info) {
   const auto iter = base::ranges::find_if(
       esim_profiles_, [activation_code](const auto& esim_profile) -> bool {
         return esim_profile->properties()->activation_code == activation_code;
@@ -153,14 +154,15 @@
   if (iter == esim_profiles_.end()) {
     NET_LOG(EVENT) << "Get pending profile with activation failed: No profile "
                       "with activation_code.";
-    return nullptr;
+    return mojom::ProfileInstallResult::kFailure;
   }
-  if ((*iter)->properties()->state != mojom::ProfileState::kPending) {
+  *profile_info = iter->get();
+  if ((*profile_info)->properties()->state != mojom::ProfileState::kPending) {
     NET_LOG(ERROR) << "Get pending profile with activation code failed: Profile"
                       "is not in pending state.";
-    return nullptr;
+    return mojom::ProfileInstallResult::kFailure;
   }
-  return iter->get();
+  return mojom::ProfileInstallResult::kSuccess;
 }
 
 ESimProfile* Euicc::GetOrCreateESimProfile(
diff --git a/chromeos/services/cellular_setup/euicc.h b/chromeos/services/cellular_setup/euicc.h
index 2fac8f8..03be5e0 100644
--- a/chromeos/services/cellular_setup/euicc.h
+++ b/chromeos/services/cellular_setup/euicc.h
@@ -63,8 +63,9 @@
                               const dbus::ObjectPath* object_path);
   void OnRequestPendingEventsResult(RequestPendingProfilesCallback callback,
                                     HermesResponseStatus status);
-  ESimProfile* GetPendingProfileInfoFromActivationCode(
-      const std::string& activation_code);
+  mojom::ProfileInstallResult GetPendingProfileInfoFromActivationCode(
+      const std::string& activation_code,
+      ESimProfile** profile_info);
   ESimProfile* GetOrCreateESimProfile(
       const dbus::ObjectPath& carrier_profile_path);
   void RemoveUntrackedProfiles(
diff --git a/chromeos/services/cellular_setup/euicc_unittest.cc b/chromeos/services/cellular_setup/euicc_unittest.cc
new file mode 100644
index 0000000..bb34a41c
--- /dev/null
+++ b/chromeos/services/cellular_setup/euicc_unittest.cc
@@ -0,0 +1,154 @@
+// 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 <utility>
+
+#include "chromeos/dbus/hermes/hermes_euicc_client.h"
+#include "chromeos/dbus/hermes/hermes_profile_client.h"
+#include "chromeos/services/cellular_setup/esim_test_base.h"
+#include "chromeos/services/cellular_setup/esim_test_utils.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+namespace {
+
+using InstallResultPair = std::pair<mojom::ProfileInstallResult,
+                                    mojo::PendingRemote<mojom::ESimProfile>>;
+
+InstallResultPair InstallProfileFromActivationCode(
+    const mojo::Remote<mojom::Euicc>& euicc,
+    const std::string& activation_code,
+    const std::string& confirmation_code) {
+  mojom::ProfileInstallResult install_result;
+  mojo::PendingRemote<mojom::ESimProfile> esim_profile;
+
+  base::RunLoop run_loop;
+  euicc->InstallProfileFromActivationCode(
+      activation_code, confirmation_code,
+      base::BindOnce(
+          [](mojom::ProfileInstallResult* out_install_result,
+             mojo::PendingRemote<mojom::ESimProfile>* out_esim_profile,
+             base::OnceClosure quit_closure,
+             mojom::ProfileInstallResult install_result,
+             mojo::PendingRemote<mojom::ESimProfile> esim_profile) {
+            *out_install_result = install_result;
+            *out_esim_profile = std::move(esim_profile);
+            std::move(quit_closure).Run();
+          },
+          &install_result, &esim_profile, run_loop.QuitClosure()));
+  run_loop.Run();
+  return std::make_pair(install_result, std::move(esim_profile));
+}
+
+mojom::ESimOperationResult RequestPendingProfiles(
+    mojo::Remote<mojom::Euicc>& euicc) {
+  mojom::ESimOperationResult result;
+  base::RunLoop run_loop;
+  euicc->RequestPendingProfiles(base::BindOnce(
+      [](mojom::ESimOperationResult* out, base::OnceClosure quit_closure,
+         mojom::ESimOperationResult result) {
+        *out = result;
+        std::move(quit_closure).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
+}  // namespace
+
+class EuiccTest : public ESimTestBase {
+ public:
+  EuiccTest() = default;
+  EuiccTest(const EuiccTest&) = delete;
+  EuiccTest& operator=(const EuiccTest&) = delete;
+
+  void SetUp() override {
+    ESimTestBase::SetUp();
+    SetupEuicc();
+  }
+};
+
+TEST_F(EuiccTest, GetProperties) {
+  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
+  ASSERT_TRUE(euicc.is_bound());
+  mojom::EuiccPropertiesPtr properties = GetEuiccProperties(euicc);
+  EXPECT_EQ(ESimTestBase::kTestEid, properties->eid);
+  EXPECT_EQ(true, properties->is_active);
+}
+
+TEST_F(EuiccTest, GetProfileList) {
+  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
+  ASSERT_TRUE(euicc.is_bound());
+  std::vector<mojo::PendingRemote<mojom::ESimProfile>> esim_profile_list =
+      GetProfileList(euicc);
+  EXPECT_EQ(0u, esim_profile_list.size());
+
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "");
+  dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+
+  esim_profile_list = GetProfileList(euicc);
+  EXPECT_EQ(2u, esim_profile_list.size());
+}
+
+TEST_F(EuiccTest, InstallProfileFromActivationCode) {
+  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
+  ASSERT_TRUE(euicc.is_bound());
+
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  // Verify that install errors return error code properly.
+  euicc_test->QueueHermesErrorStatus(
+      HermesResponseStatus::kErrorInvalidActivationCode);
+  InstallResultPair result_pair =
+      InstallProfileFromActivationCode(euicc, "", "");
+  EXPECT_EQ(mojom::ProfileInstallResult::kErrorInvalidActivationCode,
+            result_pair.first);
+  EXPECT_FALSE(result_pair.second.is_valid());
+
+  // Verify that installing a profile returns proper status code
+  // and profile object.
+  dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile(
+      dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "");
+  base::RunLoop().RunUntilIdle();
+  HermesProfileClient::Properties* dbus_properties =
+      HermesProfileClient::Get()->GetProperties(profile_path);
+  result_pair = InstallProfileFromActivationCode(
+      euicc, dbus_properties->activation_code().value(), "");
+  EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first);
+  ASSERT_TRUE(result_pair.second.is_valid());
+
+  mojo::Remote<mojom::ESimProfile> esim_profile(std::move(result_pair.second));
+  mojom::ESimProfilePropertiesPtr mojo_properties =
+      GetESimProfileProperties(esim_profile);
+  EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid);
+  EXPECT_EQ(3u, observer()->profile_list_change_calls().size());
+}
+
+TEST_F(EuiccTest, RequestPendingProfiles) {
+  mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid);
+  ASSERT_TRUE(euicc.is_bound());
+
+  HermesEuiccClient::TestInterface* euicc_test =
+      HermesEuiccClient::Get()->GetTestInterface();
+  // Verify that pending profile request errors are return properly.
+  euicc_test->QueueHermesErrorStatus(HermesResponseStatus::kErrorNoResponse);
+  EXPECT_EQ(mojom::ESimOperationResult::kFailure,
+            RequestPendingProfiles(euicc));
+  EXPECT_EQ(0u, observer()->profile_list_change_calls().size());
+
+  // Verify that successful request returns correct status code.
+  EXPECT_EQ(mojom::ESimOperationResult::kSuccess,
+            RequestPendingProfiles(euicc));
+}
+
+}  // namespace cellular_setup
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/public/cpp/BUILD.gn b/chromeos/services/cellular_setup/public/cpp/BUILD.gn
index 2d9889f..226791a 100644
--- a/chromeos/services/cellular_setup/public/cpp/BUILD.gn
+++ b/chromeos/services/cellular_setup/public/cpp/BUILD.gn
@@ -6,6 +6,8 @@
   testonly = true
 
   sources = [
+    "esim_manager_test_observer.cc",
+    "esim_manager_test_observer.h",
     "fake_activation_delegate.cc",
     "fake_activation_delegate.h",
     "fake_carrier_portal_handler.cc",
diff --git a/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.cc b/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.cc
new file mode 100644
index 0000000..5087d12
--- /dev/null
+++ b/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.cc
@@ -0,0 +1,61 @@
+// 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 "chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+ESimManagerTestObserver::ESimManagerTestObserver() = default;
+ESimManagerTestObserver::~ESimManagerTestObserver() = default;
+
+void ESimManagerTestObserver::OnAvailableEuiccListChanged() {
+  available_euicc_list_change_count_++;
+}
+
+void ESimManagerTestObserver::OnProfileListChanged(
+    mojo::PendingRemote<mojom::Euicc> euicc) {
+  profile_list_change_calls_.push_back(std::move(euicc));
+}
+
+void ESimManagerTestObserver::OnEuiccChanged(
+    mojo::PendingRemote<mojom::Euicc> euicc) {
+  euicc_change_calls_.push_back(std::move(euicc));
+}
+
+void ESimManagerTestObserver::OnProfileChanged(
+    mojo::PendingRemote<mojom::ESimProfile> esim_profile) {
+  profile_change_calls_.push_back(std::move(esim_profile));
+}
+
+mojo::PendingRemote<mojom::ESimManagerObserver>
+ESimManagerTestObserver::GenerateRemote() {
+  return receiver_.BindNewPipeAndPassRemote();
+}
+
+void ESimManagerTestObserver::Reset() {
+  available_euicc_list_change_count_ = 0;
+  profile_list_change_calls_.clear();
+  euicc_change_calls_.clear();
+  profile_change_calls_.clear();
+}
+
+mojo::PendingRemote<mojom::Euicc>
+ESimManagerTestObserver::PopLastChangedEuicc() {
+  mojo::PendingRemote<mojom::Euicc> euicc =
+      std::move(euicc_change_calls_.front());
+  euicc_change_calls_.erase(euicc_change_calls_.begin());
+  return euicc;
+}
+
+mojo::PendingRemote<mojom::ESimProfile>
+ESimManagerTestObserver::PopLastChangedESimProfile() {
+  mojo::PendingRemote<mojom::ESimProfile> esim_profile =
+      std::move(profile_change_calls_.front());
+  profile_change_calls_.erase(profile_change_calls_.begin());
+  return esim_profile;
+}
+
+}  // namespace cellular_setup
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.h b/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.h
new file mode 100644
index 0000000..bd32e8b
--- /dev/null
+++ b/chromeos/services/cellular_setup/public/cpp/esim_manager_test_observer.h
@@ -0,0 +1,67 @@
+// 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 CHROMEOS_SERVICES_CELLULAR_SETUP_PUBLIC_CPP_ESIM_MANAGER_TEST_OBSERVER_H_
+#define CHROMEOS_SERVICES_CELLULAR_SETUP_PUBLIC_CPP_ESIM_MANAGER_TEST_OBSERVER_H_
+
+#include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace chromeos {
+namespace cellular_setup {
+
+// Fake observer for testing ESimManager.
+class ESimManagerTestObserver : public mojom::ESimManagerObserver {
+ public:
+  ESimManagerTestObserver();
+  ESimManagerTestObserver(const ESimManagerTestObserver&) = delete;
+  ESimManagerTestObserver& operator=(const ESimManagerTestObserver&) = delete;
+  ~ESimManagerTestObserver() override;
+
+  // mojom::ESimManagerObserver:
+  void OnAvailableEuiccListChanged() override;
+  void OnProfileListChanged(mojo::PendingRemote<mojom::Euicc> euicc) override;
+  void OnEuiccChanged(mojo::PendingRemote<mojom::Euicc> euicc) override;
+  void OnProfileChanged(
+      mojo::PendingRemote<mojom::ESimProfile> esim_profile) override;
+
+  // Generates pending remote bound to this observer.
+  mojo::PendingRemote<mojom::ESimManagerObserver> GenerateRemote();
+
+  // Resets all counters and call lists.
+  void Reset();
+
+  // Pops the last Euicc from change calls.
+  mojo::PendingRemote<mojom::Euicc> PopLastChangedEuicc();
+
+  // Pops the last ESimProfile from change calls.
+  mojo::PendingRemote<mojom::ESimProfile> PopLastChangedESimProfile();
+
+  int available_euicc_list_change_count() {
+    return available_euicc_list_change_count_;
+  }
+  const std::vector<mojo::PendingRemote<mojom::Euicc>>&
+  profile_list_change_calls() {
+    return profile_list_change_calls_;
+  }
+  const std::vector<mojo::PendingRemote<mojom::Euicc>>& euicc_change_calls() {
+    return euicc_change_calls_;
+  }
+  const std::vector<mojo::PendingRemote<mojom::ESimProfile>>&
+  profile_change_calls() {
+    return profile_change_calls_;
+  }
+
+ private:
+  int available_euicc_list_change_count_ = 0;
+  std::vector<mojo::PendingRemote<mojom::Euicc>> profile_list_change_calls_;
+  std::vector<mojo::PendingRemote<mojom::Euicc>> euicc_change_calls_;
+  std::vector<mojo::PendingRemote<mojom::ESimProfile>> profile_change_calls_;
+  mojo::Receiver<mojom::ESimManagerObserver> receiver_{this};
+};
+
+}  // namespace cellular_setup
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_CELLULAR_SETUP_PUBLIC_CPP_ESIM_MANAGER_TEST_OBSERVER_H_
\ No newline at end of file
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 16df464..95ff56c 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -931,6 +931,10 @@
   password_autofill_agent_->InformAboutFormClearing(form);
 }
 
+void AutofillAgent::PasswordFieldReset(const WebInputElement& element) {
+  password_autofill_agent_->InformAboutFieldClearing(element);
+}
+
 void AutofillAgent::SelectWasUpdated(
     const blink::WebFormControlElement& element) {
   // Look for the form and field associated with the select element. If they are
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index d7c6810..a6d4cbe 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -196,6 +196,7 @@
   bool ShouldSuppressKeyboard(
       const blink::WebFormControlElement& element) override;
   void FormElementReset(const blink::WebFormElement& form) override;
+  void PasswordFieldReset(const blink::WebInputElement& element) override;
 
   void HandleFocusChangeComplete();
 
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index ac0c3b6..7194166 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -1384,23 +1384,47 @@
     FieldRendererId element_id(element.UniqueRendererFormControlId());
     // Notify PasswordManager if |form| has password fields that have user typed
     // input or input autofilled on user trigger.
-    if (element.FormControlTypeForAutofill() == "password" &&
-        (field_data_manager_->DidUserType(element_id) ||
-         field_data_manager_->WasAutofilledOnUserTrigger(element_id))) {
-      const form_util::ExtractMask extract_mask =
-          static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE |
-                                              form_util::EXTRACT_OPTIONS);
-      FormData form_data;
-      if (WebFormElementToFormData(form, WebFormControlElement(),
-                                   field_data_manager_.get(), extract_mask,
-                                   &form_data, nullptr)) {
-        GetPasswordManagerDriver()->PasswordFormCleared(form_data);
-      }
+    if (IsPasswordFieldFilledByUser(element)) {
+      NotifyPasswordManagerAboutClearedForm(form);
       return;
     }
   }
 }
 
+void PasswordAutofillAgent::InformAboutFieldClearing(
+    const WebInputElement& cleared_element) {
+  if (!FrameCanAccessPasswordManager())
+    return;
+  DCHECK(cleared_element.Value().IsEmpty());
+  if (!base::FeatureList::IsEnabled(
+          password_manager::features::kDetectFormSubmissionOnFormClear)) {
+    return;
+  }
+  FieldRendererId field_id(cleared_element.UniqueRendererFormControlId());
+  // Ignore fields that had no user input or autofill on user trigger.
+  if (!field_data_manager_->DidUserType(field_id) &&
+      !field_data_manager_->WasAutofilledOnUserTrigger(field_id)) {
+    return;
+  }
+
+  WebFormElement form = cleared_element.Form();
+  if (form.IsNull()) {
+    // Process password field clearing for fields outside the <form> tag.
+    GetPasswordManagerDriver()->PasswordFormCleared(
+        *GetFormDataFromUnownedInputElements());
+    return;
+  }
+  // Process field clearing for a form under a <form> tag.
+  // Only notify PasswordManager in case all user filled password fields were
+  // cleared.
+  bool cleared_all_password_fields = base::ranges::all_of(
+      form.GetFormControlElements(), [this](const auto& el) {
+        return !IsPasswordFieldFilledByUser(el) || el.Value().IsEmpty();
+      });
+  if (cleared_all_password_fields)
+    NotifyPasswordManagerAboutClearedForm(form);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // PasswordAutofillAgent, private:
 
@@ -1889,4 +1913,24 @@
          IsElementEditable(password_element);
 }
 
+bool PasswordAutofillAgent::IsPasswordFieldFilledByUser(
+    const WebFormControlElement& element) const {
+  FieldRendererId element_id(element.UniqueRendererFormControlId());
+  return element.FormControlTypeForAutofill() == "password" &&
+         (field_data_manager_->DidUserType(element_id) ||
+          field_data_manager_->WasAutofilledOnUserTrigger(element_id));
+}
+
+void PasswordAutofillAgent::NotifyPasswordManagerAboutClearedForm(
+    const WebFormElement& cleared_form) {
+  const auto extract_mask = static_cast<form_util::ExtractMask>(
+      form_util::EXTRACT_VALUE | form_util::EXTRACT_OPTIONS);
+  FormData form_data;
+  if (WebFormElementToFormData(cleared_form, WebFormControlElement(),
+                               field_data_manager_.get(), extract_mask,
+                               &form_data, nullptr)) {
+    GetPasswordManagerDriver()->PasswordFormCleared(form_data);
+  }
+}
+
 }  // namespace autofill
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index 854a25a..3e44d473 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -223,10 +223,16 @@
 
   std::unique_ptr<FormData> GetFormDataFromUnownedInputElements();
 
-  // Notification that form was cleared. This can be used as a signal of
-  // a successful submission for change password forms.
+  // Notification that form element was cleared by HTMLFormElement::reset()
+  // method. This can be used as a signal of a successful submission for change
+  // password forms.
   void InformAboutFormClearing(const blink::WebFormElement& form);
 
+  // Notification that input element was cleared by HTMLInputValue::SetValue()
+  // method by setting an empty value. This can be used as a signal of a
+  // successful submission for change password forms.
+  void InformAboutFieldClearing(const blink::WebInputElement& element);
+
   bool logging_state_active() const { return logging_state_active_; }
 
   // Determine whether the current frame is allowed to access the password
@@ -471,6 +477,15 @@
   bool CanShowPopupWithoutPasswords(
       const blink::WebInputElement& password_element) const;
 
+  // Returns true if the element is of type 'password' and has either user typed
+  // input or input autofilled on user trigger.
+  bool IsPasswordFieldFilledByUser(
+      const blink::WebFormControlElement& element) const;
+
+  // Extracts and sends the form data of |cleared_form| to PasswordManager.
+  void NotifyPasswordManagerAboutClearedForm(
+      const blink::WebFormElement& cleared_form);
+
   // The logins we have filled so far with their associated info.
   WebInputToPasswordInfoMap web_input_to_password_info_;
   // A (sort-of) reverse map to |web_input_to_password_info_|.
diff --git a/components/autofill/core/common/language_code.h b/components/autofill/core/common/language_code.h
index 532fc7d..ceec17b 100644
--- a/components/autofill/core/common/language_code.h
+++ b/components/autofill/core/common/language_code.h
@@ -9,14 +9,15 @@
 #include <string>
 #include <utility>
 
-#include "base/logging.h"
 #include "base/ranges/algorithm.h"
 #include "base/types/strong_alias.h"
 
 namespace autofill {
 
-// A LanguageCode is a two-letter lowercase abbreviation according to ISO 639-1
-// or "und", which is the ISO 639-2 code for "undetermined".
+// Following the implicit conventions in //components/translate, a LanguageCode
+// in  is a lowercase alphabetic string of length up to 3, or "zh-CN", or
+// "zh-TW". A non-exhaustive list of common values is
+// translate::kDefaultSupportedLanguages.
 class LanguageCode
     : public base::StrongAlias<class LanguageCodeTag, std::string> {
  private:
@@ -33,10 +34,8 @@
 
  private:
   void Check() {
-    LOG_IF(ERROR,
-           !(empty() ||
-             (base::ranges::all_of(value(), &islower) && length() == 2) ||
-             value() == "und" || value() == "zh-CN" || value() == "zh-TW"))
+    DCHECK((length() <= 3 && base::ranges::all_of(value(), &islower)) ||
+           value() == "zh-CN" || value() == "zh-TW")
         << "Unexpected language code '" << value() << "'";
   }
 };
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
index 1455fcf..15bd5f4 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
@@ -141,7 +141,7 @@
                             ContentSettingValues.ALLOW, ContentSettingValues.BLOCK,
                             R.string.website_settings_category_cookie_allowed, 0));
             localMap.put(ContentSettingsType.GEOLOCATION,
-                    new ResourceItem(R.drawable.permission_location,
+                    new ResourceItem(R.drawable.ic_permission_location_filled,
                             R.string.website_settings_device_location, ContentSettingValues.ASK,
                             ContentSettingValues.BLOCK,
                             R.string.website_settings_category_location_ask, 0));
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index e2c320a..32c2930 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -159,6 +159,8 @@
     "java/res/drawable/ic_music_note_24dp.xml",
     "java/res/drawable/ic_offline_pin_24dp_on_dark_bg.xml",
     "java/res/drawable/ic_offline_pin_24dp_on_light_bg.xml",
+    "java/res/drawable/ic_permission_location_filled.xml",
+    "java/res/drawable/ic_permission_location_outline.xml",
     "java/res/drawable/ic_play_circle_filled_24dp_on_dark_bg.xml",
     "java/res/drawable/ic_play_circle_filled_24dp_on_light_bg.xml",
     "java/res/drawable/ic_security_grey.xml",
@@ -167,7 +169,6 @@
     "java/res/drawable/ic_vpn_key_grey.xml",
     "java/res/drawable/ic_warning_red_16dp.xml",
     "java/res/drawable/ic_warning_red_24dp.xml",
-    "java/res/drawable/permission_location.xml",
     "java/res/drawable/smartphone_black_24dp.xml",
     "java/res/values-night/colors.xml",
     "java/res/values-night/drawables.xml",
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_permission_location_filled.xml b/components/browser_ui/styles/android/java/res/drawable/ic_permission_location_filled.xml
new file mode 100644
index 0000000..9130da4
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_permission_location_filled.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M5.25,8.6938C5.25,4.9931 8.2682,2 12,2C15.7318,2 18.75,4.9931 18.75,8.6938C18.75,13.7141 12,21.125 12,21.125C12,21.125 5.25,13.7141 5.25,8.6938ZM9.75,8.75C9.75,9.992 10.758,11 12,11C13.242,11 14.25,9.992 14.25,8.75C14.25,7.508 13.242,6.5 12,6.5C10.758,6.5 9.75,7.508 9.75,8.75Z"
+      android:fillColor="@color/default_icon_color"
+      android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/components/browser_ui/styles/android/java/res/drawable/permission_location.xml b/components/browser_ui/styles/android/java/res/drawable/ic_permission_location_outline.xml
similarity index 100%
rename from components/browser_ui/styles/android/java/res/drawable/permission_location.xml
rename to components/browser_ui/styles/android/java/res/drawable/ic_permission_location_outline.xml
diff --git a/components/embedder_support/android/BUILD.gn b/components/embedder_support/android/BUILD.gn
index 51a918f..eae62fb 100644
--- a/components/embedder_support/android/BUILD.gn
+++ b/components/embedder_support/android/BUILD.gn
@@ -70,6 +70,8 @@
     "util/response_delegate_impl.cc",
     "util/response_delegate_impl.h",
     "util/url_utilities.cc",
+    "util/user_agent_utils.cc",
+    "util/user_agent_utils.h",
     "util/web_resource_response.cc",
     "util/web_resource_response.h",
   ]
@@ -78,6 +80,7 @@
     ":util_jni_headers",
     "//base",
     "//components/google/core/common",
+    "//components/version_info",
     "//content/public/browser",
     "//mojo/public/cpp/bindings:bindings",
     "//mojo/public/cpp/system:system",
diff --git a/components/embedder_support/android/util/DEPS b/components/embedder_support/android/util/DEPS
index 839bc00..7b81183 100644
--- a/components/embedder_support/android/util/DEPS
+++ b/components/embedder_support/android/util/DEPS
@@ -1,9 +1,11 @@
 include_rules = [
   "+components/google/core/common",
+  "+components/version_info",
   "+mojo/public/cpp/bindings",
   "+mojo/public/cpp/system",
   "+net",
   "+services/network/public",
+  "+third_party/blink/public/common/user_agent",
   "+third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h",
 ]
 
diff --git a/components/embedder_support/android/util/user_agent_utils.cc b/components/embedder_support/android/util/user_agent_utils.cc
new file mode 100644
index 0000000..1a15c69
--- /dev/null
+++ b/components/embedder_support/android/util/user_agent_utils.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/embedder_support/android/util/user_agent_utils.h"
+
+#include "components/version_info/version_info.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/user_agent.h"
+#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
+
+namespace embedder_support {
+
+void SetDesktopUserAgentOverride(content::WebContents* web_contents,
+                                 const blink::UserAgentMetadata& metadata) {
+  const char kLinuxInfoStr[] = "X11; Linux x86_64";
+  std::string product = version_info::GetProductNameAndVersionForUserAgent();
+
+  blink::UserAgentOverride spoofed_ua;
+  spoofed_ua.ua_string_override =
+      content::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product);
+  spoofed_ua.ua_metadata_override = metadata;
+  spoofed_ua.ua_metadata_override->platform = "Linux";
+  spoofed_ua.ua_metadata_override->platform_version =
+      std::string();  // match content::GetOSVersion(false) on Linux
+  spoofed_ua.ua_metadata_override->architecture = "x86";
+  spoofed_ua.ua_metadata_override->model = std::string();
+  spoofed_ua.ua_metadata_override->mobile = false;
+
+  web_contents->SetUserAgentOverride(spoofed_ua, false);
+}
+
+}  // namespace embedder_support
diff --git a/components/embedder_support/android/util/user_agent_utils.h b/components/embedder_support/android/util/user_agent_utils.h
new file mode 100644
index 0000000..f2e9144
--- /dev/null
+++ b/components/embedder_support/android/util/user_agent_utils.h
@@ -0,0 +1,23 @@
+// 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 COMPONENTS_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
+#define COMPONENTS_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
+
+namespace blink {
+struct UserAgentMetadata;
+}
+
+namespace content {
+class WebContents;
+}
+
+namespace embedder_support {
+
+void SetDesktopUserAgentOverride(content::WebContents* web_contents,
+                                 const blink::UserAgentMetadata& metadata);
+
+}  // namespace embedder_support
+
+#endif  // COMPONENTS_EMBEDDER_SUPPORT_ANDROID_UTIL_USER_AGENT_UTILS_H_
diff --git a/components/feed/core/proto/wire/feed_query.proto b/components/feed/core/proto/wire/feed_query.proto
index e105058a..a4bf04e 100644
--- a/components/feed/core/proto/wire/feed_query.proto
+++ b/components/feed/core/proto/wire/feed_query.proto
@@ -8,11 +8,10 @@
 
 option optimize_for = LITE_RUNTIME;
 
+import "components/feed/core/proto/wire/chrome_fulfillment_info.proto";
 option java_package = "org.chromium.components.feed.core.proto.wire";
 option java_outer_classname = "FeedQueryProto";
 
-import "components/feed/core/proto/wire/chrome_fulfillment_info.proto";
-
 message FeedQuery {
   enum RequestReason {
     // Bucket for any not listed. Should not be used (prefer adding a new
diff --git a/components/optimization_guide/proto/models.proto b/components/optimization_guide/proto/models.proto
index dabc1cd..2c9e177 100644
--- a/components/optimization_guide/proto/models.proto
+++ b/components/optimization_guide/proto/models.proto
@@ -247,6 +247,8 @@
   MODEL_TYPE_UNKNOWN = 0;
   // A decision tree.
   MODEL_TYPE_DECISION_TREE = 1;
+  // A model using only operations that are supported by TensorflowLite 2.3.0.
+  MODEL_TYPE_TFLITE_2_3_0 = 2;
 }
 
 // A set of model features and the host that it applies to.
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index 4f0b519..67d6ce6 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -14,6 +14,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -61,6 +62,7 @@
 using autofill::SINGLE_USERNAME;
 using autofill::UNKNOWN_TYPE;
 using autofill::USERNAME;
+using autofill::mojom::SubmissionIndicatorEvent;
 using base::NumberToString;
 using BlacklistedStatus =
     password_manager::OriginCredentialStore::BlacklistedStatus;
@@ -1231,16 +1233,27 @@
 void PasswordManager::OnPasswordFormCleared(
     PasswordManagerDriver* driver,
     const autofill::FormData& form_data) {
-  // Find a form with corresponding renderer id.
-  auto it = base::ranges::find_if(form_managers_, [&](auto& manager) {
-    return manager->DoesManageAccordingToRendererId(
-        form_data.unique_renderer_id, driver);
-  });
-  if (it != form_managers_.end() && (*it)->is_submitted() &&
-      (*it)->GetSubmittedForm()->IsPossibleChangePasswordForm()) {
-    (*it)->UpdateSubmissionIndicatorEvent(
-        autofill::mojom::SubmissionIndicatorEvent::
-            CHANGE_PASSWORD_FORM_CLEARED);
+  PasswordFormManager* manager = GetMatchedManager(driver, form_data);
+  if (!manager || !manager->is_submitted() ||
+      !manager->GetSubmittedForm()->IsPossibleChangePasswordForm()) {
+    return;
+  }
+  // If a password form was cleared, login is successful.
+  if (form_data.is_form_tag) {
+    manager->UpdateSubmissionIndicatorEvent(
+        SubmissionIndicatorEvent::CHANGE_PASSWORD_FORM_CLEARED);
+    OnLoginSuccessful();
+    return;
+  }
+  // If password fields outside the <form> tag were cleared, it should be
+  // verified that fields are relevant.
+  FieldRendererId new_password_field_id =
+      manager->GetSubmittedForm()->new_password_element_renderer_id;
+  auto it = base::ranges::find(form_data.fields, new_password_field_id,
+                               &autofill::FormFieldData::unique_renderer_id);
+  if (it != form_data.fields.end() && it->value.empty()) {
+    manager->UpdateSubmissionIndicatorEvent(
+        SubmissionIndicatorEvent::CHANGE_PASSWORD_FORM_CLEARED);
     OnLoginSuccessful();
   }
 }
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index f629462..3a94b32 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -3739,8 +3739,6 @@
   old_password_field.value = ASCIIToUTF16("oldpass");
   form_data.fields.push_back(old_password_field);
 
-  // Form changes: new and confirmation password fields are added by the
-  // website's scripts.
   FormFieldData new_password_field;
   new_password_field.form_control_type = "password";
   new_password_field.unique_renderer_id = FieldRendererId(2);
@@ -3767,6 +3765,67 @@
       .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
   manager()->OnPasswordFormCleared(&driver_, form_data);
 }
+
+TEST_P(PasswordManagerTest, SubmissionDetectedOnClearedFormlessFields) {
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  PasswordForm saved_match(MakeSavedForm());
+  EXPECT_CALL(*store_, GetLogins)
+      .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match)));
+
+  for (bool new_password_field_was_cleared : {true, false}) {
+    SCOPED_TRACE(testing::Message("#new password field was cleared = ")
+                 << new_password_field_was_cleared);
+
+    // Create FormData for a form with 1 password field and process it.
+    FormData form_data;
+    form_data.is_form_tag = false;
+    form_data.unique_renderer_id = FormRendererId(0);
+    form_data.url = GURL("http://www.google.com/a/LoginAuth");
+
+    FormFieldData old_password_field;
+    old_password_field.form_control_type = "password";
+    old_password_field.unique_renderer_id = FieldRendererId(1);
+    old_password_field.name = ASCIIToUTF16("oldpass");
+    old_password_field.value = ASCIIToUTF16("oldpass");
+    form_data.fields.push_back(old_password_field);
+
+    FormFieldData new_password_field;
+    new_password_field.form_control_type = "password";
+    new_password_field.unique_renderer_id = FieldRendererId(2);
+    new_password_field.name = ASCIIToUTF16("newpass");
+    new_password_field.autocomplete_attribute = "new-password";
+    form_data.fields.push_back(new_password_field);
+
+    FormFieldData confirm_password_field;
+    confirm_password_field.form_control_type = "password";
+    confirm_password_field.unique_renderer_id = FieldRendererId(3);
+    confirm_password_field.name = ASCIIToUTF16("confpass");
+    form_data.fields.push_back(confirm_password_field);
+
+    manager()->OnPasswordFormsParsed(&driver_, {form_data});
+
+    form_data.fields[0].value = ASCIIToUTF16("oldpass");
+    form_data.fields[1].value = ASCIIToUTF16("newpass");
+    form_data.fields[2].value = ASCIIToUTF16("newpass");
+
+    manager()->OnInformAboutUserInput(&driver_, form_data);
+
+    form_data.fields[0].value = base::string16();
+    form_data.fields[2].value = base::string16();
+    if (new_password_field_was_cleared)
+      form_data.fields[1].value = base::string16();
+
+    std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save;
+    if (new_password_field_was_cleared) {
+      EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr)
+          .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
+    } else {
+      EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr).Times(0);
+    }
+
+    manager()->OnPasswordFormCleared(&driver_, form_data);
+  }
+}
 #endif  // !defined(OS_IOS)
 
 TEST_P(PasswordManagerTest, IsFormManagerPendingPasswordUpdate) {
diff --git a/components/performance_manager/graph/frame_node_impl.h b/components/performance_manager/graph/frame_node_impl.h
index 48c4d3a..c3b7175 100644
--- a/components/performance_manager/graph/frame_node_impl.h
+++ b/components/performance_manager/graph/frame_node_impl.h
@@ -359,7 +359,8 @@
   // Indicates if the frame is visible. This is initialized in
   // FrameNodeImpl::OnJoiningGraph() and then maintained by
   // FrameVisibilityDecorator.
-  ObservedProperty::NotifiesOnlyOnChanges<
+  ObservedProperty::NotifiesOnlyOnChangesWithPreviousValue<
+      Visibility,
       Visibility,
       &FrameNodeObserver::OnFrameVisibilityChanged>
       visibility_{Visibility::kUnknown};
diff --git a/components/performance_manager/graph/frame_node_impl_unittest.cc b/components/performance_manager/graph/frame_node_impl_unittest.cc
index 85794cf..7d423b7 100644
--- a/components/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/components/performance_manager/graph/frame_node_impl_unittest.cc
@@ -149,7 +149,8 @@
   MOCK_METHOD1(OnHadFormInteractionChanged, void(const FrameNode*));
   MOCK_METHOD1(OnIsAudibleChanged, void(const FrameNode*));
   MOCK_METHOD1(OnViewportIntersectionChanged, void(const FrameNode*));
-  MOCK_METHOD1(OnFrameVisibilityChanged, void(const FrameNode*));
+  MOCK_METHOD2(OnFrameVisibilityChanged,
+               void(const FrameNode*, FrameNode::Visibility));
   MOCK_METHOD1(OnNonPersistentNotificationCreated, void(const FrameNode*));
   MOCK_METHOD2(OnFirstContentfulPaint, void(const FrameNode*, base::TimeDelta));
 
@@ -396,7 +397,8 @@
   MockObserver obs;
   graph()->AddFrameNodeObserver(&obs);
 
-  EXPECT_CALL(obs, OnFrameVisibilityChanged(frame_node.get()));
+  EXPECT_CALL(obs, OnFrameVisibilityChanged(
+                       frame_node.get(), FrameNode::Visibility::kNotVisible));
 
   frame_node->SetVisibility(FrameNode::Visibility::kVisible);
   EXPECT_EQ(frame_node->visibility(), FrameNode::Visibility::kVisible);
diff --git a/components/performance_manager/public/graph/frame_node.h b/components/performance_manager/public/graph/frame_node.h
index a00bb84..fa4e846 100644
--- a/components/performance_manager/public/graph/frame_node.h
+++ b/components/performance_manager/public/graph/frame_node.h
@@ -249,7 +249,9 @@
   virtual void OnViewportIntersectionChanged(const FrameNode* frame_node) = 0;
 
   // Invoked when the visibility property changes.
-  virtual void OnFrameVisibilityChanged(const FrameNode* frame_node) = 0;
+  virtual void OnFrameVisibilityChanged(
+      const FrameNode* frame_node,
+      FrameNode::Visibility previous_value) = 0;
 
   // Events with no property changes.
 
@@ -296,7 +298,9 @@
   void OnHadFormInteractionChanged(const FrameNode* frame_node) override {}
   void OnIsAudibleChanged(const FrameNode* frame_node) override {}
   void OnViewportIntersectionChanged(const FrameNode* frame_node) override {}
-  void OnFrameVisibilityChanged(const FrameNode* frame_node) override {}
+  void OnFrameVisibilityChanged(const FrameNode* frame_node,
+                                FrameNode::Visibility previous_value) override {
+  }
   void OnNonPersistentNotificationCreated(
       const FrameNode* frame_node) override {}
   void OnFirstContentfulPaint(
diff --git a/components/policy/resources/policy_templates_de.xtb b/components/policy/resources/policy_templates_de.xtb
index 871d13e..5047485 100644
--- a/components/policy/resources/policy_templates_de.xtb
+++ b/components/policy/resources/policy_templates_de.xtb
@@ -3489,9 +3489,6 @@
 <translation id="6536600139108165863">Automatischer Neustart nach Herunterfahren des Geräts</translation>
 <translation id="6539246272469751178">Diese Richtlinie hat keine Auswirkung auf Android-Apps. Android-Apps verwenden immer das Standardverzeichnis für Downloads und haben keinen Zugriff auf Dateien, die von <ph name="PRODUCT_OS_NAME" /> in ein anderes Verzeichnis als das Standardverzeichnis für Downloads heruntergeladen werden.</translation>
 <translation id="654303922206238013">Migrationsstrategie für eCryptfs</translation>
-<translation id="6544897973797372144">Wenn diese Richtlinie auf "True" gesetzt ist und die Richtlinie "ChromeOsReleaseChannel" nicht angegeben ist, können Nutzer der Anmelde-Domain den Release-Kanal des Geräts ändern. Ist diese Richtlinie auf "False" gesetzt, wird auf dem Gerät der zuletzt eingestellte Kanal verwendet und dieser kann nicht geändert werden.
-
-      Der vom Nutzer ausgewählte Kanal wird durch die Richtlinie "ChromeOsReleaseChannel" außer Kraft gesetzt. Falls der Kanal der Richtlinie jedoch stabiler ist als der auf dem Gerät installierte Kanal, wird der Kanal erst gewechselt, nachdem der stabilere Kanal eine höhere Versionsnummer als der auf dem Gerät installierte Kanal erreicht hat.</translation>
 <translation id="6553143066970470539">Displayhelligkeit in %</translation>
 <translation id="6558362593755624474">Mit dieser Richtlinie wird der <ph name="PLUGIN_VM_NAME" />-Lizenzschlüssel für dieses Gerät angegeben.</translation>
 <translation id="6559057113164934677">Keine Website darf auf meine Kamera oder mein Mikrofon zugreifen</translation>
@@ -4263,7 +4260,6 @@
       Diese Richtlinie gilt nur für Kinder.
       Wenn diese Richtlinie festgelegt ist, kann der Elternzugriffscode auf dem Gerät des Kindes überprüft werden.
       Ist die Richtlinie nicht festgelegt, kann der Elternzugriffscode auf dem Gerät des Kindes nicht überprüft werden.</translation>
-<translation id="7625444193696794922">Gibt den Release-Kanal an, an den dieses Gerät gebunden werden soll.</translation>
 <translation id="7629840767216985001">Wenn Sie die Richtlinie auf "True" setzen, wird der große Cursor auf dem Anmeldebildschirm aktiviert. Wenn Sie die Richtlinie auf "False" setzen, wird der große Cursor auf dem Anmeldebildschirm deaktiviert.
 
       Wenn Sie diese Richtlinie konfigurieren, können Nutzer vorübergehend den großen Cursor aktivieren oder deaktivieren. Wenn der Anmeldebildschirm neu geladen wird oder eine Minute lang inaktiv bleibt, wird er in den Originalzustand zurückversetzt.
diff --git a/components/policy/resources/policy_templates_es-419.xtb b/components/policy/resources/policy_templates_es-419.xtb
index 636e5a3..d37d384 100644
--- a/components/policy/resources/policy_templates_es-419.xtb
+++ b/components/policy/resources/policy_templates_es-419.xtb
@@ -3507,9 +3507,6 @@
 <translation id="6536600139108165863">Reinicio automático cuando se apaga el dispositivo</translation>
 <translation id="6539246272469751178">Esta política no tiene efecto en las apps de Android. Estas apps siempre usan el directorio de descargas predeterminado y no pueden acceder a ningún archivo que haya descargado <ph name="PRODUCT_OS_NAME" /> en un directorio de descargas no predeterminado.</translation>
 <translation id="654303922206238013">Estrategia de migración para eCryptfs</translation>
-<translation id="6544897973797372144">Si esta política se establece en Verdadera y la política ChromeOsReleaseChannel no se especifica, los usuarios del dominio de inscripción podrán cambiar el canal de versiones del dispositivo. Si esta política se establece en Falsa, el dispositivo se bloqueará en cualquier canal que se haya establecido anteriormente.
-
-      El canal seleccionado por el usuario será reemplazado por la política ChromeOsReleaseChannel, pero si el canal de la política es más estable que el que fue instalado en el dispositivo, el canal solo cambiará después de que la versión del canal más estable alcance un número de versión más alto que la instalada en el dispositivo.</translation>
 <translation id="6553143066970470539">Porcentaje del brillo de pantalla</translation>
 <translation id="6558362593755624474">Si estableces la política, se especificará la clave de licencia <ph name="PLUGIN_VM_NAME" /> para este dispositivo.</translation>
 <translation id="6559057113164934677">No permitir que ningún sitio acceda a la cámara ni al micrófono</translation>
@@ -4280,7 +4277,6 @@
       Esta política solo se aplica al usuario menor de edad.
       Si se establece esta política, el código de acceso superior podrá verificarse en el dispositivo del usuario menor de edad.
       Si no se establece, no es posible verificar el código en esos dispositivos.</translation>
-<translation id="7625444193696794922">Especifica el canal de publicaciones al que debe conectarse este dispositivo.</translation>
 <translation id="7629840767216985001">Si estableces el valor "True" para la política, se activará el cursor grande en la pantalla de acceso. Si estableces el valor "False", se desactivará el cursor en esa pantalla.
 
       Si configuras la política, los usuarios podrán activar o desactivar el cursor grande de forma temporal. Cuando la pantalla de acceso se actualice o permanezca inactiva durante un minuto, el cursor se revertirá a su estado original.
diff --git a/components/policy/resources/policy_templates_es.xtb b/components/policy/resources/policy_templates_es.xtb
index 2c4e215..de47cc7 100644
--- a/components/policy/resources/policy_templates_es.xtb
+++ b/components/policy/resources/policy_templates_es.xtb
@@ -3528,9 +3528,6 @@
 <translation id="6536600139108165863">Reinicio automático al cerrar el dispositivo</translation>
 <translation id="6539246272469751178">Esta política no afecta a las aplicaciones para Android. Siempre utilizan el directorio de descargas predeterminado y no pueden acceder a ningún archivo que <ph name="PRODUCT_OS_NAME" /> haya descargado en un directorio de descargas que no sea el predeterminado.</translation>
 <translation id="654303922206238013">Estrategia de migración para eCryptfs</translation>
-<translation id="6544897973797372144">Si el valor correspondiente a esta política se establece en True y no se especifica la política ChromeOsReleaseChannel, los usuarios del dominio pertinente podrán cambiar el canal de lanzamiento del dispositivo. Si el valor de esta política se establece en False, el dispositivo se bloqueará en el último canal configurado.
-
-      La política ChromeOsReleaseChannel anulará el canal seleccionado por el usuario, pero, si el canal de la política es más estable que el instalado en el dispositivo, este se podrá cambiar únicamente después de que la versión del canal más estable supere a la instalada en el dispositivo.</translation>
 <translation id="6553143066970470539">Porcentaje de brillo de la pantalla</translation>
 <translation id="6558362593755624474">Establecer esta política permite especificar la clave de licencia de <ph name="PLUGIN_VM_NAME" /> para este dispositivo.</translation>
 <translation id="6559057113164934677">No permitir que ningún sitio acceda a la cámara y al micrófono</translation>
@@ -4306,7 +4303,6 @@
       Esta política solo se aplica a usuarios menores de edad.
       Cuando esta política está activada, el código de acceso parental se puede verificar en el dispositivo del menor.
       Cuando esta política está desactivada, no se puede verificar el código de acceso parental en el dispositivo del menor.</translation>
-<translation id="7625444193696794922">Permite especificar el canal de lanzamiento al que se vinculará este dispositivo.</translation>
 <translation id="7629840767216985001">Si se le asigna el valor "true" a esta política, el cursor grande se activa en la pantalla de inicio de sesión. Si se le asigna el valor "false", el cursor grande se desactiva en la pantalla de inicio de sesión.
 
       Si se configura esta política, los usuarios pueden activar y desactivar temporalmente el cursor grande. Cuando la pantalla de inicio de sesión se vuelve a cargar o permanece inactiva durante un minuto, vuelve a su estado original.
diff --git a/components/policy/resources/policy_templates_fr.xtb b/components/policy/resources/policy_templates_fr.xtb
index faedddc..656bf6f 100644
--- a/components/policy/resources/policy_templates_fr.xtb
+++ b/components/policy/resources/policy_templates_fr.xtb
@@ -3516,9 +3516,6 @@
 <translation id="6536600139108165863">Redémarrer automatiquement à l'arrêt de l'appareil</translation>
 <translation id="6539246272469751178">Cette règle n'a aucun effet sur les applications Android. Celles-ci se servent toujours du répertoire de téléchargements par défaut. Elles ne peuvent pas accéder aux fichiers téléchargés par <ph name="PRODUCT_OS_NAME" /> dans un autre répertoire de téléchargements.</translation>
 <translation id="654303922206238013">Stratégie de migration pour eCryptfs</translation>
-<translation id="6544897973797372144">Si cette règle est définie sur "True", et si la règle ChromeOsReleaseChannel n'est pas spécifiée, les utilisateurs du domaine correspondant sont autorisés à modifier la version de l'appareil. Si elle est définie sur "False", l'appareil est verrouillé sur la dernière version configurée.
-
-      La règle ChromeOsReleaseChannel prévaut sur la version sélectionnée par l'utilisateur. Toutefois, si cette dernière est plus stable que la version installée sur l'appareil, elle n'est remplacée qu'une fois que la version la plus stable atteint un numéro supérieur à celui de la version installée sur l'appareil.</translation>
 <translation id="6553143066970470539">Niveau de luminosité de l'écran (%)</translation>
 <translation id="6558362593755624474">Cette règle permet de spécifier la clé de licence <ph name="PLUGIN_VM_NAME" /> pour cet appareil.</translation>
 <translation id="6559057113164934677">Interdire à tous les sites d'accéder à la caméra et au microphone</translation>
@@ -4287,7 +4284,6 @@
       Cette règle s'applique uniquement aux enfants.
       Lorsque cette règle est activée, le code d'accès parental peut être validé sur l'appareil d'un enfant.
       Lorsqu'elle est désactivée, il est impossible de valider ce code sur l'appareil d'un enfant.</translation>
-<translation id="7625444193696794922">Indique le canal de distribution sur lequel cet appareil devrait être verrouillé.</translation>
 <translation id="7629840767216985001">Définissez cette règle sur "True" pour activer le grand curseur sur l'écran de connexion. Définissez-la sur "False" pour désactiver le grand curseur sur l'écran de connexion.
 
       Si vous définissez cette règle, les utilisateurs peuvent temporairement activer ou désactiver le grand curseur. Lorsque l'écran de connexion s'actualise ou qu'il reste inactif pendant une minute, il revient à son état d'origine.
diff --git a/components/policy/resources/policy_templates_id.xtb b/components/policy/resources/policy_templates_id.xtb
index 46bf03be..dce8a65 100644
--- a/components/policy/resources/policy_templates_id.xtb
+++ b/components/policy/resources/policy_templates_id.xtb
@@ -3523,9 +3523,6 @@
 <translation id="6536600139108165863">Booting ulang otomatis saat mematikan perangkat</translation>
 <translation id="6539246272469751178">Kebijakan ini tidak memengaruhi aplikasi Android. Aplikasi Android selalu menggunakan direktori download default dan tidak dapat mengakses file apa pun yang didownload oleh <ph name="PRODUCT_OS_NAME" /> ke direktori download non-default.</translation>
 <translation id="654303922206238013">Strategi migrasi untuk ecryptfs</translation>
-<translation id="6544897973797372144">Jika kebijakan ini disetel ke True dan kebijakan ChromeOsReleaseChannel tidak ditentukan, maka pengguna domain yang mendaftar akan diizinkan mengubah saluran rilis perangkat. Jika kebijakan ini disetel ke false, perangkat akan dikunci pada saluran apa pun yang disetel terakhir kali.
-
-      Saluran yang dipilih pengguna akan ditimpa oleh kebijakan ChromeOsReleaseChannel, namun jika saluran kebijakan lebih stabil dari saluran kebijakan yang telah terpasang pada perangkat, maka saluran ini hanya akan beralih setelah versi saluran yang lebih stabil mencapai jumlah versi yang lebih tinggi dari saluran kebijakan yang telah terpasang pada perangkat.</translation>
 <translation id="6553143066970470539">Persentase kecerahan layar</translation>
 <translation id="6558362593755624474">Menyetel kebijakan akan menentukan kunci lisensi <ph name="PLUGIN_VM_NAME" /> untuk perangkat ini.</translation>
 <translation id="6559057113164934677">Jangan izinkan situs apa pun mengakses kamera dan mikrofon</translation>
@@ -4296,7 +4293,6 @@
       Kebijakan ini hanya berlaku untuk pengguna anak.
       Jika kebijakan ini disetel, Kode Akses Orang Tua dapat diverifikasi di perangkat pengguna anak.
       Jika kebijakan ini tidak disetel, Kode Akses Orang Tua tidak dapat diverifikasi di perangkat pengguna anak.</translation>
-<translation id="7625444193696794922">Menentukan saluran rilis yang harus dikunci oleh perangkat ini.</translation>
 <translation id="7629840767216985001">Jika kebijakan ditetapkan ke Benar (True), kursor besar akan diaktifkan di layar login. Jika kebijakan ditetapkan ke Salah (False), kursor besar akan dinonaktifkan di layar login.
 
       Jika kebijakan ditetapkan, pengguna akan dapat mengaktifkan atau menonaktifkan kursor besar untuk sementara. Saat dimuat ulang atau tidak ada aktivitas selama satu menit, layar login akan kembali ke keadaan semula.
diff --git a/components/policy/resources/policy_templates_it.xtb b/components/policy/resources/policy_templates_it.xtb
index cda8fd6..8cf66a7 100644
--- a/components/policy/resources/policy_templates_it.xtb
+++ b/components/policy/resources/policy_templates_it.xtb
@@ -3504,9 +3504,6 @@
 <translation id="6536600139108165863">Riavvia automaticamente allo spegnimento del dispositivo</translation>
 <translation id="6539246272469751178">Questa norma non influisce sulle app Android, che utilizzano sempre la directory per i download predefinita e non possono accedere ai file scaricati da <ph name="PRODUCT_OS_NAME" /> in una directory per i download non predefinita.</translation>
 <translation id="654303922206238013">Strategia di migrazione per ecryptfs</translation>
-<translation id="6544897973797372144">Se questa norma è impostata su True e non è specificata la norma ChromeOsReleaseChannel, gli utenti del dominio di registrazione avranno la facoltà di modificare il canale della versione del dispositivo. Se questa norma è impostata su False, il dispositivo risulterà bloccato in qualunque canale in cui è stato impostato per ultimo.
-
-      Il canale selezionato dall'utente verrà sostituito dalla norma ChromeOsReleaseChannel, ma se il canale della norma è più stabile di quello che è stato installato sul dispositivo, il canale cambierà solo quando la versione del canale più stabile raggiungerà un numero di versione superiore rispetto a quello installato sul dispositivo.</translation>
 <translation id="6553143066970470539">Percentuale di luminosità dello schermo</translation>
 <translation id="6558362593755624474">La configurazione del criterio consente di specificare il codice licenza <ph name="PLUGIN_VM_NAME" /> per il dispositivo.</translation>
 <translation id="6559057113164934677">Non consentire ad alcun sito di accedere alla fotocamera e al microfono</translation>
@@ -4279,7 +4276,6 @@
       Questa norma viene applicata solo agli utenti che sono bambini o ragazzi.
       Quando questa norma è impostata, il codice di accesso genitori può essere verificato sul dispositivo dell'utente bambino o ragazzo.
       Quando questa norma non è impostata, non è possibile verificare il codice di accesso genitori sul dispositivo dell'utente bambino o ragazzo.</translation>
-<translation id="7625444193696794922">Consente di specificare il canale di rilascio su cui deve essere bloccato questo dispositivo.</translation>
 <translation id="7629840767216985001">Se il criterio è impostato su True, viene attivato il cursore grande nella schermata di accesso. Se il criterio è impostato su False, viene disattivato il cursore grande nella schermata di accesso.
 
       Se imposti il criterio, gli utenti possono attivare o disattivare temporaneamente il cursore grande. Quando la schermata di accesso viene ricaricata o rimane inattiva per un minuto, il cursore torna allo stato originale.
diff --git a/components/policy/resources/policy_templates_ja.xtb b/components/policy/resources/policy_templates_ja.xtb
index a5f0693..9f17fc3 100644
--- a/components/policy/resources/policy_templates_ja.xtb
+++ b/components/policy/resources/policy_templates_ja.xtb
@@ -3435,9 +3435,6 @@
 <translation id="6536600139108165863">デバイスのシャットダウン時に自動的に再起動する</translation>
 <translation id="6539246272469751178">このポリシーは Android アプリには適用されません。Android アプリは、常にデフォルトのダウンロード ディレクトリを使用し、<ph name="PRODUCT_OS_NAME" /> によってデフォルト以外のダウンロード ディレクトリにダウンロードされたファイルにはアクセスできません。</translation>
 <translation id="654303922206238013">ecryptfs の移行方法</translation>
-<translation id="6544897973797372144">このポリシーを true に設定し、ChromeOsReleaseChannel ポリシーを指定していない場合、登録したドメインのユーザーに、デバイスのリリース チャンネルの変更を許可します。このポリシーを false に設定すると、デバイスは、最後に設定されたチャンネルに固定されます。
-
-      ユーザーが選択したチャンネルよりも ChromeOsReleaseChannel ポリシーが優先されます。ただし、ポリシーのチャンネルが、デバイスにインストールされたチャンネルよりも安定している場合、ポリシーのチャンネルのバージョン番号がデバイスにインストールされているチャンネルよりも大きくなったときのみチャンネルが切り替わります。</translation>
 <translation id="6553143066970470539">画面の明るさの割合(%)</translation>
 <translation id="6558362593755624474">このポリシーでは、このデバイスの <ph name="PLUGIN_VM_NAME" /> のライセンスキーを指定します。</translation>
 <translation id="6559057113164934677">カメラやマイクへのアクセスをどのサイトにも許可しない</translation>
@@ -4183,7 +4180,6 @@
 |future_config| はアクセスコードの確認に使用されるメインの設定です。|old_configs| は、|future_config| と |current_config| でアクセスコードを確認できなかった場合に限り確認に使用されます。
 
 このポリシーは、アクセスコード設定を段階的に切り替える場合に使用することを想定しています。新しい設定は常に |future_config| に入力され、同時に既存の値は |current_config| に移動されます。|current_config| の以前の値は |old_configs| に移動され、切り替えが完了した後に削除されます。このポリシーは子どものユーザーにのみ適用されます。このポリシーが設定されている場合、子どものユーザーのデバイスで保護者のアクセスコードを確認できます。このポリシーが設定されていない場合、子どものユーザーのデバイスで保護者のアクセスコードを確認できません。</translation>
-<translation id="7625444193696794922">このデバイスを固定するリリース チャンネルを指定します。</translation>
 <translation id="7629840767216985001">このポリシーを True に設定した場合、ログイン画面で大きいカーソルが有効になります。このポリシーを False に設定した場合、ログイン画面で大きいカーソルが無効になります。
 
       このポリシーを設定した場合、ユーザーは大きいカーソルを一時的に有効または無効にできます。ログイン画面が再読み込みされるか 1 分間アイドル状態が続くと、元の状態に戻ります。
diff --git a/components/policy/resources/policy_templates_ko.xtb b/components/policy/resources/policy_templates_ko.xtb
index acce0ace..c0f67098 100644
--- a/components/policy/resources/policy_templates_ko.xtb
+++ b/components/policy/resources/policy_templates_ko.xtb
@@ -3522,9 +3522,6 @@
 <translation id="6536600139108165863">기기 종료 시 자동 재부팅</translation>
 <translation id="6539246272469751178">이 정책은 Android 앱에 아무런 영향도 미치지 않습니다. Android 앱은 언제나 기본 다운로드 디렉터리를 사용하며 <ph name="PRODUCT_OS_NAME" />에 의해 기본 다운로드 디렉터리가 아닌 디렉터리로 다운로드된 파일에 액세스할 수 없습니다.</translation>
 <translation id="654303922206238013">ecryptfs 관련 이전 전략</translation>
-<translation id="6544897973797372144">정책이 True로 설정되어 있고 ChromeOsReleaseChannel 정책이 지정되지 않은 경우 등록된 도메인의 사용자가 기기의 배포 채널을 변경할 수 있도록 허용됩니다. 정책이 false로 설정되어 있으면 마지막에 설정한 채널을 기기가 사용합니다.
-
-      사용자가 선택한 채널은 ChromeOsReleaseChannel 정책이 무시할 수 있지만 정책 채널이 기기에 설치된 채널보다 더 안정적인 경우 더 안정적인 채널이 기기에 설치된 채널보다 더 많아질 때 채널이 전환됩니다.</translation>
 <translation id="6553143066970470539">화면 밝기 퍼센트</translation>
 <translation id="6558362593755624474">정책을 설정하면 이 기기의 <ph name="PLUGIN_VM_NAME" /> 라이선스 키가 지정됩니다.</translation>
 <translation id="6559057113164934677">카메라 및 마이크에 대한 모든 사이트의 액세스 허용 안함</translation>
@@ -4301,7 +4298,6 @@
       이 정책은 자녀 사용자에게만 적용됩니다.
       이 정책이 설정되어 있으면 자녀 사용자의 기기에서 부모 액세스 코드를 확인할 수 있습니다.
       이 정책이 설정되어 있지 않으면 자녀 사용자의 기기에서 부모 액세스 코드를 확인할 수 없습니다.</translation>
-<translation id="7625444193696794922">기기에서 사용해야 할 특정 배포 채널을 지정합니다.</translation>
 <translation id="7629840767216985001">이 정책을 True로 설정하면 로그인 화면에서 큰 커서가 사용 설정됩니다. 정책을 False로 설정하면 로그인 화면에서 큰 커서가 사용 중지됩니다.
 
       정책을 설정하면 사용자는 일시적으로 큰 커서를 사용 설정하거나 사용 중지할 수 있습니다. 로그인 화면이 새로고침되거나 1분 정도 유휴 상태가 유지되면 원래 상태로 돌아갑니다.
diff --git a/components/policy/resources/policy_templates_nl.xtb b/components/policy/resources/policy_templates_nl.xtb
index e828aeb..1f64963 100644
--- a/components/policy/resources/policy_templates_nl.xtb
+++ b/components/policy/resources/policy_templates_nl.xtb
@@ -3545,9 +3545,6 @@
 <translation id="6536600139108165863">Automatisch opnieuw opstarten wanneer apparaat wordt afgesloten</translation>
 <translation id="6539246272469751178">Dit beleid is niet van invloed op Android-apps. Android-apps gebruiken altijd de standaardmap voor downloads en hebben geen toegang tot bestanden die door <ph name="PRODUCT_OS_NAME" /> zijn gedownload naar een niet-standaard downloadmap.</translation>
 <translation id="654303922206238013">Migratiestrategie voor eCryptfs</translation>
-<translation id="6544897973797372144">Als dit beleid is ingesteld op 'True' en het beleid ChromeOsReleaseChannel niet is ingesteld, mogen gebruikers van het registrerende domein het releasekanaal van het apparaat wijzigen. Als dit beleid is ingesteld op 'False', wordt het apparaat vergrendeld op het laatst ingestelde kanaal.
-
-      Het door de gebruiker geselecteerde kanaal wordt overschreven door het beleid ChromeOsReleaseChannel. Als het beleidskanaal echter stabieler is dan het kanaal dat is geïnstalleerd op het apparaat, wordt het kanaal pas gewijzigd nadat de versie van het stabielere kanaal een hoger versienummer bereikt dan de versie die op het apparaat is geïnstalleerd.</translation>
 <translation id="6553143066970470539">Percentage voor helderheid van scherm</translation>
 <translation id="6558362593755624474">Als je het beleid instelt, kun je de <ph name="PLUGIN_VM_NAME" />-licentiecode voor dit apparaat opgeven.</translation>
 <translation id="6559057113164934677">Niet toestaan dat sites toegang krijgen tot de camera en microfoon</translation>
@@ -4332,7 +4329,6 @@
       Dit beleid is alleen van toepassing op gebruikers met een kinderaccount.
       Wanneer dit beleid is ingesteld, kan de toegangscode voor ouders worden geverifieerd op het apparaat van de gebruiker met een kinderaccount.
       Wanneer dit beleid niet is ingesteld, kan de toegangscode voor ouders niet worden geverifieerd op het apparaat van de gebruiker met een kinderaccount.</translation>
-<translation id="7625444193696794922">Hiermee wordt het releasekanaal gespecificeerd waarmee dit apparaat moet worden vergrendeld.</translation>
 <translation id="7629840767216985001">Als je het beleid instelt op True, wordt de grote cursor ingeschakeld op het inlogscherm. Als je het beleid instelt op False, wordt de grote cursor uitgeschakeld op het inlogscherm.
 
       Als je dit beleid instelt, kunnen gebruikers de grote cursor tijdelijk in- of uitschakelen. Als het inlogscherm opnieuw wordt geladen of een minuut lang niet wordt gebruikt, wordt de oorspronkelijke status hersteld.
diff --git a/components/policy/resources/policy_templates_pt-BR.xtb b/components/policy/resources/policy_templates_pt-BR.xtb
index 446f473e..0915e45 100644
--- a/components/policy/resources/policy_templates_pt-BR.xtb
+++ b/components/policy/resources/policy_templates_pt-BR.xtb
@@ -3484,9 +3484,6 @@
 <translation id="6536600139108165863">Reinicialização automática no desligamento do dispositivo</translation>
 <translation id="6539246272469751178">Esta política não tem nenhum efeito sobre apps Android. Os apps Android sempre usam o diretório de downloads padrão e não podem acessar nenhum arquivo transferido por download pelo <ph name="PRODUCT_OS_NAME" /> para um diretório de downloads não padrão.</translation>
 <translation id="654303922206238013">Estratégia de migração para eCryptfs</translation>
-<translation id="6544897973797372144">Se esta política for definida como "True", e a política ChromeOsReleaseChannel não for especificada, os usuários do domínio de inscrição poderão alterar o canal de versão do dispositivo. Se esta política for definida como "false", o dispositivo será bloqueado em qualquer canal em que a política tenha sido definida pela última vez.
-
-      O canal selecionado pelo usuário será substituído pela política ChromeOsReleaseChannel, mas se o canal da política for mais estável que aquele instalado no dispositivo, o canal só vai mudar depois que a versão do canal mais estável atingir um número de versão superior à que está instalada no dispositivo.</translation>
 <translation id="6553143066970470539">Porcentagem de brilho da tela</translation>
 <translation id="6558362593755624474">A definição da política especifica a chave de licença de <ph name="PLUGIN_VM_NAME" /> para este dispositivo.</translation>
 <translation id="6559057113164934677">Não permitir que nenhum site acesse minha câmera e meu microfone</translation>
@@ -4257,7 +4254,6 @@
       Esta política se aplica apenas a usuários menores de idade.
       Quando esta política é definida, o código de acesso dos pais pode ser verificado no dispositivo da criança.
       Quando esta política não é definida, não é possível verificar o código de acesso dos pais no dispositivo da criança.</translation>
-<translation id="7625444193696794922">Especifica o canal de liberação ao qual este dispositivo deve ser vinculado.</translation>
 <translation id="7629840767216985001">Se esta política for definida como verdadeira, o cursor grande será ativado na tela de login. Se esta política for definida como falsa, o cursor grande será desativado na tela de login.
 
       Se você definir a política, os usuários poderão ativar ou desativar o cursor grande temporariamente. Quando a tela de login for atualizada ou ficar inativa por um minuto, o cursor retornará ao estado original.
diff --git a/components/policy/resources/policy_templates_ru.xtb b/components/policy/resources/policy_templates_ru.xtb
index 02977b1..4db66845 100644
--- a/components/policy/resources/policy_templates_ru.xtb
+++ b/components/policy/resources/policy_templates_ru.xtb
@@ -3497,9 +3497,6 @@
 <translation id="6536600139108165863">Автоматическая перезагрузка при выключении устройства</translation>
 <translation id="6539246272469751178">Это правило не влияет на приложения Android, поскольку они используют каталог скачанных файлов по умолчанию. Файлы, скачанные <ph name="PRODUCT_OS_NAME" /> в другой каталог, им недоступны.</translation>
 <translation id="654303922206238013">Стратегия перехода с шифрования ecryptfs</translation>
-<translation id="6544897973797372144">Если для этого правила установлено значение True, а правило ChromeOsReleaseChannel не настроено, пользователи регистрируемого домена могут менять канал обновления устройства. Если установлено значение False, используется канал, который был настроен в прошлый раз.
-
-      Канал обновления, выбранный пользователем, переопределяется значением ChromeOsReleaseChannel. Однако если канал, заданный этим правилом, стабильнее того, который установлен на устройстве, то он будет изменен только после появления более новой стабильной версии.</translation>
 <translation id="6553143066970470539">Уровень яркости экрана в процентах</translation>
 <translation id="6558362593755624474">Правило позволяет указать лицензионный ключ <ph name="PLUGIN_VM_NAME" /> для этого устройства.</translation>
 <translation id="6559057113164934677">Запретить сайтам доступ к камере и микрофону</translation>
@@ -4269,7 +4266,6 @@
       Это правило применяется только к детским аккаунтам.
       Если оно задано, родительский код доступа можно подтвердить на устройстве ребенка.
       Если правило не настроено, подтвердить родительский код доступа на устройстве ребенка нельзя.</translation>
-<translation id="7625444193696794922">Задает канал выпуска, за которым должно быть закреплено устройство.</translation>
 <translation id="7629840767216985001">Если для правила задано значение True, на экране входа будет включен большой курсор. Если для правила задано значение False, большой курсор на экране входа будет выключен.
 
       Если вы настроите это правило, пользователи смогут временно включить или выключить большой курсор. При перезагрузке экрана входа или после одной минуты бездействия будет восстановлено исходное состояние курсора.
diff --git a/components/policy/resources/policy_templates_th.xtb b/components/policy/resources/policy_templates_th.xtb
index 434606ff..863f712 100644
--- a/components/policy/resources/policy_templates_th.xtb
+++ b/components/policy/resources/policy_templates_th.xtb
@@ -3476,9 +3476,6 @@
 <translation id="6536600139108165863">เริ่มต้นใหม่โดยอัตโนมัติเมื่ออุปกรณ์ปิดเครื่อง</translation>
 <translation id="6539246272469751178">นโยบายนี้ไม่ส่งผลต่อแอป Android โดยแอป Android จะใช้ไดเรกทอรีการดาวน์โหลดเริ่มต้นเสมอ และไม่สามารถเข้าถึงไฟล์ใดๆ ที่ดาวน์โหลดโดย <ph name="PRODUCT_OS_NAME" /> ลงในไดเรกทอรีการดาวน์โหลดที่ไม่ใช่ค่าเริ่มต้น</translation>
 <translation id="654303922206238013">กลยุทธ์การย้ายข้อมูลสำหรับ eCryptfs</translation>
-<translation id="6544897973797372144">หากนโยบายนี้มีการกำหนดค่าเป็น "จริง" และไม่ได้ระบุนโยบาย ChromeOsReleaseChannel ไว้ ผู้ใช้ในโดเมนที่ลงทะเบียนจะได้รับอนุญาตให้เปลี่ยนแปลงช่องสำหร้บเปิดตัวการอัปเดตของอุปกรณ์ได้ หากนโยบายถูกกำหนดค่าเป็น "เท็จ" อุปกรณ์จะถูกล็อกในช่องใดก็ตามที่ถูกตั้งค่าไว้ล่าสุด
-
-      ช่องที่ผู้ใช้เลือกจะถูกแทนที่โดยนโยบาย ChromeOsReleaseChannel แต่ถ้าช่องนโยบายมีความเสถียรมากกว่าช่องที่ติดตั้งบนอุปกรณ์ ช่องดังกล่าวจะเปิด/ปิดใช้งานหลังจากที่ช่องที่เสถียรมากกว่าอัปเกรดไปจนถึงรุ่นที่สูงกว่าช่องที่ติดตั้งบนอุปกรณ์</translation>
 <translation id="6553143066970470539">เปอร์เซ็นต์ความสว่างหน้าจอ</translation>
 <translation id="6558362593755624474">การตั้งค่านโยบายจะระบุรหัสสัญญาอนุญาต <ph name="PLUGIN_VM_NAME" /> สำหรับอุปกรณ์นี้</translation>
 <translation id="6559057113164934677">ไม่อนุญาตให้ไซต์ใดๆ เข้าถึงกล้องและไมโครโฟน</translation>
@@ -4249,7 +4246,6 @@
       นโยบายนี้ใช้กับผู้ใช้ที่เป็นเด็กเท่านั้น
       เมื่อตั้งค่านโยบายนี้ คุณจะยืนยันรหัสการเข้าถึงของผู้ปกครองในอุปกรณ์ของผู้ใช้ที่เป็นเด็กได้
       เมื่อไม่ได้ตั้งค่านโยบายนี้ คุณจะยืนยันรหัสการเข้าถึงของผู้ปกครองในอุปกรณ์ของผู้ใช้ที่เป็นเด็กไม่ได้</translation>
-<translation id="7625444193696794922">ระบุช่องทางแสดงผลที่ควรจะล็อกเข้ากับอุปกรณ์นี้</translation>
 <translation id="7629840767216985001">การตั้งค่านโยบายเป็น "จริง" จะเปิดใช้เคอร์เซอร์ขนาดใหญ่ในหน้าจอลงชื่อเข้าใช้ การตั้งค่านโยบายเป็น "เท็จ" จะปิดใช้เคอร์เซอร์ขนาดใหญ่ในหน้าจอลงชื่อเข้าใช้
 
       หากตั้งค่านโยบายไว้ ผู้ใช้จะเปิดหรือปิดใช้เคอร์เซอร์ขนาดใหญ่ได้ชั่วคราว เมื่อหน้าจอลงชื่อเข้าใช้โหลดซ้ำหรือไม่มีการใช้งานเป็นเวลา 1 นาที เคอร์เซอร์จะเปลี่ยนกลับไปอยู่ในสถานะเดิม
diff --git a/components/policy/resources/policy_templates_tr.xtb b/components/policy/resources/policy_templates_tr.xtb
index 76ceb83d..71dc98a 100644
--- a/components/policy/resources/policy_templates_tr.xtb
+++ b/components/policy/resources/policy_templates_tr.xtb
@@ -3491,9 +3491,6 @@
 <translation id="6536600139108165863">Cihaz kapandığında otomatik olarak yenide başlat</translation>
 <translation id="6539246272469751178">Bu politikanın Android uygulamaları üzerinde hiçbir etkisi yoktur. Android uygulamaları daima varsayılan indirme dizinini kullanır ve <ph name="PRODUCT_OS_NAME" /> tarafından, varsayılan olmayan indirme dizinine indirilen dosyalara erişemez.</translation>
 <translation id="654303922206238013">ecryptfs için taşıma stratejisi</translation>
-<translation id="6544897973797372144">Bu politika True değerine ayarlanırsa ve ChromeOsReleaseChannel politikası belirtilmediyse, kaydedilmekte olan alan adı kullanıcılarının cihazın yayın kanalını değiştirmelerine izin verilir. Bu politika false değerine ayarlanırsa, cihaz son olarak ayarlandığı kanalda kilitlenir.
-
-      Kullanıcının seçtiği kanal ChromeOsReleaseChannel politikası tarafından geçersiz kılınırsa, ancak politika kanalı cihaza yüklenen kanaldan daha dengeliyse; kanal yalnızca daha dengeli kanalın sürümü cihazda yüklü olandan daha yüksek bir sürüme ulaştığında değiştirilir.</translation>
 <translation id="6553143066970470539">Ekran parlaklığı yüzdesi</translation>
 <translation id="6558362593755624474">Politikanın ayarlanması, bu cihaz için <ph name="PLUGIN_VM_NAME" /> lisans anahtarını belirtir.</translation>
 <translation id="6559057113164934677">Hiçbir sitenin kamera ve mikrofona erişmesine izin verme</translation>
@@ -4269,7 +4266,6 @@
       Bu politika yalnızca çocuk kullanıcılar için geçerlidir.
       Bu politika ayarlandığında, Ebeveyn Erişim Kodu çocuk kullanıcının cihazında doğrulanabilir.
       Bu politika ayarlanmadığında, Ebeveyn Erişim Kodu çocuk kullanıcının cihazında doğrulanamaz.</translation>
-<translation id="7625444193696794922">Bu cihazın kilitlenmesi gereken yayın kanalını belirtir.</translation>
 <translation id="7629840767216985001">Politikayı True (Doğru) değerine ayarlamak, oturum açma ekranında büyük imleci açar. Politikayı False (Yanlış) değerine ayarlamak, oturum açma ekranında büyük imleci kapatır.
 
       Politikayı ayarlarsanız kullanıcılar büyük imleci geçici olarak açabilir veya kapatabilir. Oturum açma ekranı yeniden yüklendiğinde veya bir dakika boşta kaldığında orijinal durumuna geri döner.
diff --git a/components/policy/resources/policy_templates_uk.xtb b/components/policy/resources/policy_templates_uk.xtb
index 89c3059..92e3e39 100644
--- a/components/policy/resources/policy_templates_uk.xtb
+++ b/components/policy/resources/policy_templates_uk.xtb
@@ -3521,9 +3521,6 @@
 <translation id="6536600139108165863">Автоматично перезапускати, коли пристрій вимикається</translation>
 <translation id="6539246272469751178">Це правило не впливає на додатки Android. Додатки Android завжди використовують каталог завантажень за умовчанням і не мають доступу до файлів, завантажених через <ph name="PRODUCT_OS_NAME" /> в інші каталоги.</translation>
 <translation id="654303922206238013">Стратегія міграції даних із шифрування eCryptfs</translation>
-<translation id="6544897973797372144">Якщо для цього правила встановлено значення "true", а правило ChromeOsReleaseChannel не вказано, користувачі домену реєстрації зможуть змінювати версію каналу пристрою. Якщо для цього правила встановлено значення "false", пристрій буде заблоковано в каналі, який налаштовувався останнім.
-
-      Вибраний користувачем канал замінюватиметься правилом ChromeOsReleaseChannel. Проте якщо канал правила стабільніший за встановлений на пристрої, перехід на інший канал відбудеться лише за появи номера версії стабільнішого каналу, вищого за номер версії каналу, установленого на пристрої.</translation>
 <translation id="6553143066970470539">Відсоток яскравості екрана</translation>
 <translation id="6558362593755624474">За допомогою параметрів цього правила можна вказати ключ ліцензії <ph name="PLUGIN_VM_NAME" /> для цього пристрою.</translation>
 <translation id="6559057113164934677">Заборонити всім сайтам доступ до камери та мікрофона</translation>
@@ -4298,7 +4295,6 @@
       Це правило стосується лише користувачів-дітей.
       Коли його налаштовано, батьківський код доступу можна підтвердити на пристрої дитини.
       Коли правило не налаштовано, батьківський код доступу не можна підтвердити на пристрої дитини.</translation>
-<translation id="7625444193696794922">Указує версію випуску, з якою потрібно пов’язати цей пристрій.</translation>
 <translation id="7629840767216985001">Якщо для цього правила вибрано значення True, великий курсор на екрані входу буде ввімкнено. Якщо для нього вибрано значення False, великий курсор на екрані входу буде вимкнено.
 
       Якщо ви налаштуєте це правило, користувачі зможуть тимчасово вмикати або вимикати великий курсор. Якщо екран входу оновиться або користувач буде неактивним упродовж однієї хвилини, буде відновлено початковий розмір курсора.
diff --git a/components/policy/resources/policy_templates_vi.xtb b/components/policy/resources/policy_templates_vi.xtb
index da27d57..d471d60 100644
--- a/components/policy/resources/policy_templates_vi.xtb
+++ b/components/policy/resources/policy_templates_vi.xtb
@@ -3529,9 +3529,6 @@
 <translation id="6536600139108165863">Tự động khởi động lại khi tắt thiết bị</translation>
 <translation id="6539246272469751178">Chính sách này không ảnh hưởng đến ứng dụng Android. Ứng dụng Android luôn sử dụng thư mục nội dung tải xuống mặc định và không thể truy cập bất kỳ tệp nào do <ph name="PRODUCT_OS_NAME" /> tải xuống thư mục nội dung tải xuống không phải mặc định.</translation>
 <translation id="654303922206238013">Chiến lược di chuyển cho ecryptfs</translation>
-<translation id="6544897973797372144">Nếu chính sách này được đặt thành Đúng và chính sách ChromeOsReleaseChannel không được chỉ định thì người dùng của miền đăng ký sẽ được phép thay đổi kênh phát hành của thiết bị. Nếu chính sách này được đặt thành sai, thiết bị sẽ bị khóa bất kể kênh mà thiết bị được đặt lần cuối.
-
-      Kênh do người dùng đã chọn sẽ bị ghi đè bởi chính sách ChromeOsReleaseChannel nhưng nếu kênh của chính sách ổn định hơn kênh được cài đặt trên thiết bị thì kênh sẽ chỉ chuyển đổi sau khi phiên bản của kênh ổn định hơn đạt tới số phiên bản cao hơn phiên bản được cài đặt trên thiết bị.</translation>
 <translation id="6553143066970470539">Phần trăm độ sáng màn hình</translation>
 <translation id="6558362593755624474">Việc đặt chính sách này sẽ chỉ định khóa cấp phép <ph name="PLUGIN_VM_NAME" /> cho thiết bị này.</translation>
 <translation id="6559057113164934677">Không cho phép bất kỳ trang web nào truy cập vào máy ảnh và micrô</translation>
@@ -4308,7 +4305,6 @@
       Chính sách này chỉ áp dụng cho người dùng là trẻ em.
       Khi bạn đặt chính sách này, Mã truy cập dành cho cha mẹ có thể được xác minh trên thiết bị của người dùng là trẻ em.
       Khi bạn không đặt chính sách này, Mã truy cập dành cho cha mẹ không xác minh được trên thiết bị của người dùng là trẻ em.</translation>
-<translation id="7625444193696794922">Chỉ định kênh phát hành mà thiết bị này phải bị khóa.</translation>
 <translation id="7629840767216985001">Khi bạn đặt chính sách này thành True, con trỏ lớn sẽ bật trên màn hình đăng nhập. Khi bạn đặt chính sách này thành False, con trỏ lớn sẽ tắt trên màn hình đăng nhập.
 
       Nếu bạn đặt chính sách này, thì người dùng có thể tạm thời bật hoặc tắt con trỏ lớn. Khi màn hình đăng nhập tải lại hoặc không hoạt động trong vài phút, con trỏ lớn sẽ trở về trạng thái ban đầu.
diff --git a/components/policy/resources/policy_templates_zh-CN.xtb b/components/policy/resources/policy_templates_zh-CN.xtb
index 6188594..e52ec24 100644
--- a/components/policy/resources/policy_templates_zh-CN.xtb
+++ b/components/policy/resources/policy_templates_zh-CN.xtb
@@ -3470,9 +3470,6 @@
 <translation id="6536600139108165863">设备关机后自动重新启动</translation>
 <translation id="6539246272469751178">此政策对 Android 应用没有任何影响。Android 应用始终都会使用默认下载目录,并且无法访问由 <ph name="PRODUCT_OS_NAME" />下载到非默认下载目录中的任何文件。</translation>
 <translation id="654303922206238013">ecryptfs 迁移策略</translation>
-<translation id="6544897973797372144">如果该策略设置为 True,且未指定 ChromeOsReleaseChannel 策略,那么注册域的用户将可以更改设备的发布版。如果该策略设置为 False,那么设备就会锁定最后设置的任何版本。
-
-     ChromeOsReleaseChannel 策略将会覆盖用户选择的版本,但是,如果该策略版本比设备上已安装的版本更稳定,那么仅当较稳定版本的版本值高于设备上已安装版本时,系统才会切换版本。</translation>
 <translation id="6553143066970470539">屏幕亮度百分比</translation>
 <translation id="6558362593755624474">通过设置此政策,您可为该设备指定 <ph name="PLUGIN_VM_NAME" /> 许可密钥。</translation>
 <translation id="6559057113164934677">不允许任何网站使用摄像头和麦克风</translation>
@@ -4241,7 +4238,6 @@
       此政策仅适用于未成年用户。
       如果设置了此政策,则可在未成年用户的设备上验证家长访问码。
       如果未设置此政策,则无法在未成年用户的设备上验证家长访问码。</translation>
-<translation id="7625444193696794922">指定该设备应锁定到的发布渠道。</translation>
 <translation id="7629840767216985001">将此政策设为 True 时,系统会在登录屏幕上开启大号光标。将此政策设为 False 时,系统会在登录屏幕上关闭大号光标。
 
       如果您设置了此政策,用户便可暂时开启或关闭大号光标。当登录屏幕重新加载或闲置了 1 分钟后,大号光标就会还原为初始状态。
diff --git a/components/policy/resources/policy_templates_zh-TW.xtb b/components/policy/resources/policy_templates_zh-TW.xtb
index 5e51a29..2b031a5 100644
--- a/components/policy/resources/policy_templates_zh-TW.xtb
+++ b/components/policy/resources/policy_templates_zh-TW.xtb
@@ -3460,9 +3460,6 @@
 <translation id="6536600139108165863">裝置關機時自動重新啟動</translation>
 <translation id="6539246272469751178">這項政策對 Android 應用程式沒有影響。Android 應用程式會一律使用預設的下載目錄,無法存取任何由 <ph name="PRODUCT_OS_NAME" />下載到非預設下載目錄的檔案。</translation>
 <translation id="654303922206238013">eCryptfs 遷移策略</translation>
-<translation id="6544897973797372144">如果你將這項政策設為「True」,且並未指定 ChromeOsReleaseChannel 政策,則註冊網域的使用者將可變更裝置的發布頻道。如果你將這項政策設為「False」,則系統會將裝置鎖定為先前設定的任何頻道。
-
-      ChromeOsReleaseChannel 政策會覆寫使用者選取的頻道,但如果政策設定的頻道比裝置原本安裝的頻道更穩定,則系統只會在更穩定的頻道發布比裝置上安裝的頻道更高的版本時,才會切換至該版本。</translation>
 <translation id="6553143066970470539">螢幕亮度百分比</translation>
 <translation id="6558362593755624474">你可以透過這項政策,指定這部裝置的 <ph name="PLUGIN_VM_NAME" /> 授權金鑰。</translation>
 <translation id="6559057113164934677">不允許任何網站使用攝影機和麥克風</translation>
@@ -4228,7 +4225,6 @@
 
 這項政策應用於逐步輪換存取碼設定。新設定一律會加到 |future_config| 中,同時
       現有值會移到 |current_config| 中。|current_config| 先前的值則會移到 |old_configs|,並在輪換結束後移除。這項政策只適用於兒童使用者。如果設定這項政策,系統將可在兒童使用者裝置上驗證家長存取碼。如果不設定這項政策,系統將無法在兒童使用者裝置上驗證家長存取碼。</translation>
-<translation id="7625444193696794922">指定裝置的固定發布頻道。</translation>
 <translation id="7629840767216985001">將這項政策設為 True 時,系統會在登入畫面啟用大型游標。將這項政策設為 False 時,系統會在登入畫面停用大型游標。
 
       如果設定這項政策,使用者可暫時啟用或停用大型游標。當登入畫面重新載入或閒置達一分鐘時,大型游標會還原成原始狀態。
diff --git a/components/safe_browsing/core/features.cc b/components/safe_browsing/core/features.cc
index f95c21f..a5b0aa3f 100644
--- a/components/safe_browsing/core/features.cc
+++ b/components/safe_browsing/core/features.cc
@@ -36,6 +36,9 @@
 const base::Feature kClientSideDetectionForAndroid{
     "ClientSideDetectionModelOnAndroid", base::FEATURE_DISABLED_BY_DEFAULT};
 
+extern const base::Feature kClientSideDetectionModelVersion{
+    "ClientSideDetectionModel", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kDelayedWarnings{"SafeBrowsingDelayedWarnings",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/components/safe_browsing/core/features.h b/components/safe_browsing/core/features.h
index 7655c04a..da13e82 100644
--- a/components/safe_browsing/core/features.h
+++ b/components/safe_browsing/core/features.h
@@ -37,6 +37,10 @@
 // Enables client side detection on Android.
 extern const base::Feature kClientSideDetectionForAndroid;
 
+// Determines the experimental version of client side detection model, for
+// Desktop.
+extern const base::Feature kClientSideDetectionModelVersion;
+
 // Enable the addition of access tokens to download pings for enhanced
 // protection users.
 extern const base::Feature kDownloadRequestWithToken;
diff --git a/components/translate/core/browser/language_state.h b/components/translate/core/browser/language_state.h
index 130a1ce..ea810ce 100644
--- a/components/translate/core/browser/language_state.h
+++ b/components/translate/core/browser/language_state.h
@@ -52,11 +52,15 @@
   // Returns true if the current page in the associated tab has been translated.
   bool IsPageTranslated() const { return original_lang_ != current_lang_; }
 
-  void SetOriginalLanguage(const std::string& language);
+  // Returns the original language represented as a lowercase alphabetic string
+  // of length 0 to 3 or "zh-CN" or "zh-TW".
   const std::string& original_language() const { return original_lang_; }
+  void SetOriginalLanguage(const std::string& language);
 
-  void SetCurrentLanguage(const std::string& language);
+  // Returns the current language represented as a lowercase alphabetic string
+  // of length 0 to 3 or "zh-CN" or "zh-TW".
   const std::string& current_language() const { return current_lang_; }
+  void SetCurrentLanguage(const std::string& language);
 
   bool page_needs_translation() const { return page_needs_translation_; }
 
diff --git a/components/viz/service/display/overlay_processor_interface.cc b/components/viz/service/display/overlay_processor_interface.cc
index dd3fddf..2264a492 100644
--- a/components/viz/service/display/overlay_processor_interface.cc
+++ b/components/viz/service/display/overlay_processor_interface.cc
@@ -117,7 +117,10 @@
   }
 
   gpu::SharedImageInterface* sii = nullptr;
-  if (features::ShouldUseRealBuffersForPageFlipTest()) {
+  if (features::ShouldUseRealBuffersForPageFlipTest() &&
+      ui::OzonePlatform::GetInstance()
+          ->GetOverlayManager()
+          ->allow_sync_and_real_buffer_page_flip_testing()) {
     sii = shared_image_interface;
     CHECK(shared_image_interface);
   }
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index a850683e..5765295c 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -2766,20 +2766,63 @@
       text_after_iframe.As(&text_after_iframe_iaccessible2_4));
 
   LONG n_ranges = 1;
-  IA2Range* ranges =
+  IA2Range* cross_tree_ranges =
       reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
-  ranges[0].anchor = text_in_iframe.Get();
-  ranges[0].anchorOffset = 0;
-  ranges[0].active = text_after_iframe.Get();
-  ranges[0].activeOffset = 2;
+  cross_tree_ranges[0].anchor = text_in_iframe.Get();
+  cross_tree_ranges[0].anchorOffset = 0;
+  cross_tree_ranges[0].active = text_after_iframe.Get();
+  cross_tree_ranges[0].activeOffset = 2;
 
   // This is expected to fail because the anchor and focus nodes are in
   // different trees, which Blink doesn't support.
-  EXPECT_HRESULT_FAILED(
-      text_after_iframe_iaccessible2_4->setSelectionRanges(n_ranges, ranges));
+  EXPECT_HRESULT_FAILED(text_after_iframe_iaccessible2_4->setSelectionRanges(
+      n_ranges, cross_tree_ranges));
 
-  CoTaskMemFree(ranges);
-  ranges = nullptr;
+  CoTaskMemFree(cross_tree_ranges);
+  cross_tree_ranges = nullptr;
+
+  // Now test a variation where the selection start and end are in the same
+  // tree as each other, but a different tree than the caller.
+  IA2Range* same_tree_ranges =
+      reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
+  same_tree_ranges[0].anchor = text_in_iframe.Get();
+  same_tree_ranges[0].anchorOffset = 0;
+  same_tree_ranges[0].active = text_in_iframe.Get();
+  same_tree_ranges[0].activeOffset = 1;
+
+  // This should succeed, however the selection will need to be queried from
+  // a node in the iframe tree.
+  AccessibilityNotificationWaiter selection_waiter(
+      shell()->web_contents(), ui::kAXModeComplete,
+      ax::mojom::Event::kTextSelectionChanged);
+  ASSERT_HRESULT_SUCCEEDED(text_after_iframe_iaccessible2_4->setSelectionRanges(
+      n_ranges, same_tree_ranges));
+  selection_waiter.WaitForNotification();
+
+  Microsoft::WRL::ComPtr<IAccessible2_4> text_in_iframe_iaccessible2_4;
+  ASSERT_HRESULT_SUCCEEDED(text_in_iframe.As(&text_in_iframe_iaccessible2_4));
+
+  IA2Range* result_ranges =
+      reinterpret_cast<IA2Range*>(CoTaskMemAlloc(sizeof(IA2Range)));
+  HRESULT hr = text_in_iframe_iaccessible2_4->get_selectionRanges(
+      &result_ranges, &n_ranges);
+  EXPECT_EQ(S_OK, hr);
+  EXPECT_EQ(1, n_ranges);
+  ASSERT_NE(nullptr, result_ranges);
+  ASSERT_NE(nullptr, result_ranges[0].anchor);
+  EXPECT_EQ(text_in_iframe.Get(), result_ranges[0].anchor);
+  EXPECT_EQ(0, result_ranges[0].anchorOffset);
+  ASSERT_NE(nullptr, result_ranges[0].active);
+  EXPECT_EQ(text_in_iframe.Get(), result_ranges[0].active);
+  EXPECT_EQ(1, result_ranges[0].activeOffset);
+
+  same_tree_ranges[0].anchor->Release();
+  same_tree_ranges[0].active->Release();
+  CoTaskMemFree(same_tree_ranges);
+  same_tree_ranges = nullptr;
+
+  CoTaskMemFree(result_ranges);
+  result_ranges = nullptr;
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityWinBrowserTest, TestMultiLineSetSelection) {
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index b2aacc2..71c6590 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1820,11 +1820,25 @@
       manager_->SetScrollOffset(*this, data.target_point);
       return true;
     case ax::mojom::Action::kSetSelection: {
-      // "data.anchor_offset" and "data.focus_ofset" might need to be adjusted
-      // if the anchor or the focus nodes include ignored children.
       ui::AXActionData selection = data;
+
+      // Prioritize target_tree_id if it was provided, as it is possible on
+      // some platforms (such as IAccessible2) to initiate a selection in a
+      // different tree than the current node resides in, as long as the nodes
+      // being selected share an AXTree with each other.
+      BrowserAccessibilityManager* selection_manager = nullptr;
+      if (selection.target_tree_id != ui::AXTreeIDUnknown()) {
+        selection_manager =
+            BrowserAccessibilityManager::FromID(selection.target_tree_id);
+      } else {
+        selection_manager = manager_;
+      }
+      DCHECK(selection_manager);
+
+      // "data.anchor_offset" and "data.focus_offset" might need to be adjusted
+      // if the anchor or the focus nodes include ignored children.
       const BrowserAccessibility* anchor_object =
-          manager()->GetFromID(selection.anchor_node_id);
+          selection_manager->GetFromID(selection.anchor_node_id);
       DCHECK(anchor_object);
       if (!anchor_object->PlatformIsLeaf()) {
         DCHECK_GE(selection.anchor_offset, 0);
@@ -1843,8 +1857,12 @@
       }
 
       const BrowserAccessibility* focus_object =
-          manager()->GetFromID(selection.focus_node_id);
+          selection_manager->GetFromID(selection.focus_node_id);
       DCHECK(focus_object);
+
+      // Blink only supports selections between two nodes in the same tree.
+      DCHECK_EQ(anchor_object->GetTreeData().tree_id,
+                focus_object->GetTreeData().tree_id);
       if (!focus_object->PlatformIsLeaf()) {
         DCHECK_GE(selection.focus_offset, 0);
         const BrowserAccessibility* focus_child =
@@ -1859,7 +1877,7 @@
         }
       }
 
-      manager_->SetSelection(selection);
+      selection_manager->SetSelection(selection);
       return true;
     }
     case ax::mojom::Action::kSetValue:
diff --git a/content/browser/android/dialog_overlay_impl.cc b/content/browser/android/dialog_overlay_impl.cc
index 3552dd1..ac283b7 100644
--- a/content/browser/android/dialog_overlay_impl.cc
+++ b/content/browser/android/dialog_overlay_impl.cc
@@ -12,6 +12,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "gpu/ipc/common/gpu_surface_tracker.h"
+#include "media/mojo/mojom/android_overlay.mojom.h"
 #include "ui/android/view_android_observer.h"
 #include "ui/android/window_android.h"
 
@@ -252,6 +253,20 @@
   }
 }
 
+static void JNI_DialogOverlayImpl_NotifyDestroyedSynchronously(
+    JNIEnv* env,
+    int message_pipe_handle) {
+  mojo::MessagePipeHandle handle(message_pipe_handle);
+  mojo::ScopedMessagePipeHandle scoped_handle(std::move(handle));
+  mojo::Remote<media::mojom::AndroidOverlayClient> remote(
+      mojo::PendingRemote<media::mojom::AndroidOverlayClient>(
+          std::move(scoped_handle),
+          media::mojom::AndroidOverlayClient::Version_));
+  remote->OnSynchronouslyDestroyed();
+  // Note that we don't take back the mojo message pipe.  We let it close when
+  // `remote` goes out of scope.
+}
+
 static jint JNI_DialogOverlayImpl_RegisterSurface(
     JNIEnv* env,
     const JavaParamRef<jobject>& surface) {
diff --git a/content/browser/font_access/font_access_manager_impl.cc b/content/browser/font_access/font_access_manager_impl.cc
index 2738a71..b914894 100644
--- a/content/browser/font_access/font_access_manager_impl.cc
+++ b/content/browser/font_access/font_access_manager_impl.cc
@@ -15,6 +15,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/font_access/font_enumeration_table.pb.h"
 #include "third_party/blink/public/mojom/frame/lifecycle.mojom-shared.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
 
@@ -105,6 +106,28 @@
 #endif
 }
 
+void FontAccessManagerImpl::FindAllFonts(FindAllFontsCallback callback) {
+#if !defined(PLATFORM_HAS_LOCAL_FONT_ENUMERATION_IMPL)
+  std::move(callback).Run(blink::mojom::FontEnumerationStatus::kUnimplemented,
+                          {});
+#else
+  // Obtain cached font enumeration.
+  ipc_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](FontAccessManagerImpl* impl, FindAllFontsCallback callback,
+             scoped_refptr<base::TaskRunner> results_task_runner) {
+            FontEnumerationCache::GetInstance()
+                ->QueueShareMemoryRegionWhenReady(
+                    results_task_runner,
+                    base::BindOnce(&FontAccessManagerImpl::DidFindAllFonts,
+                                   base::Unretained(impl),
+                                   std::move(callback)));
+          },
+          base::Unretained(this), std::move(callback), results_task_runner_));
+#endif
+}
+
 void FontAccessManagerImpl::DidRequestPermission(
     EnumerateLocalFontsCallback callback,
     blink::mojom::PermissionStatus status) {
@@ -134,4 +157,35 @@
 #endif
 }
 
+void FontAccessManagerImpl::DidFindAllFonts(
+    FindAllFontsCallback callback,
+    blink::mojom::FontEnumerationStatus status,
+    base::ReadOnlySharedMemoryRegion region) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (status != blink::mojom::FontEnumerationStatus::kOk) {
+    std::move(callback).Run(status, {});
+    return;
+  }
+
+  const base::ReadOnlySharedMemoryMapping mapping = region.Map();
+  if (mapping.size() > INT_MAX) {
+    std::move(callback).Run(
+        blink::mojom::FontEnumerationStatus::kUnexpectedError, {});
+    return;
+  }
+
+  blink::FontEnumerationTable table;
+  table.ParseFromArray(mapping.memory(), static_cast<int>(mapping.size()));
+
+  std::vector<blink::mojom::FontMetadata> data;
+  for (const auto& element : table.fonts()) {
+    auto entry = blink::mojom::FontMetadata(
+        element.postscript_name(), element.full_name(), element.family());
+    data.push_back(std::move(entry));
+  }
+
+  std::move(callback).Run(status, std::move(data));
+}
+
 }  // namespace content
diff --git a/content/browser/font_access/font_access_manager_impl.h b/content/browser/font_access/font_access_manager_impl.h
index 616e2be..6d39e3d 100644
--- a/content/browser/font_access/font_access_manager_impl.h
+++ b/content/browser/font_access/font_access_manager_impl.h
@@ -7,6 +7,7 @@
 
 #include "base/sequence_checker.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/font_access_context.h"
 #include "content/public/browser/global_routing_id.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
@@ -15,7 +16,8 @@
 namespace content {
 
 class CONTENT_EXPORT FontAccessManagerImpl
-    : public blink::mojom::FontAccessManager {
+    : public blink::mojom::FontAccessManager,
+      public FontAccessContext {
  public:
   FontAccessManagerImpl();
   ~FontAccessManagerImpl() override;
@@ -39,6 +41,9 @@
   // blink.mojom.FontAccessManager:
   void EnumerateLocalFonts(EnumerateLocalFontsCallback callback) override;
 
+  // content::FontAccessContext:
+  void FindAllFonts(FindAllFontsCallback callback) override;
+
   void SkipPrivacyChecksForTesting(bool skip) {
     skip_privacy_checks_for_testing_ = skip;
   }
@@ -46,8 +51,13 @@
  private:
   void DidRequestPermission(EnumerateLocalFontsCallback callback,
                             blink::mojom::PermissionStatus status);
+  void DidFindAllFonts(FindAllFontsCallback callback,
+                       blink::mojom::FontEnumerationStatus,
+                       base::ReadOnlySharedMemoryRegion);
+
   // Registered clients.
   mojo::ReceiverSet<blink::mojom::FontAccessManager, BindingContext> receivers_;
+
   scoped_refptr<base::SequencedTaskRunner> ipc_task_runner_;
   scoped_refptr<base::TaskRunner> results_task_runner_;
 
diff --git a/content/browser/font_access/font_access_manager_impl_unittest.cc b/content/browser/font_access/font_access_manager_impl_unittest.cc
index ba465cf..8da1540 100644
--- a/content/browser/font_access/font_access_manager_impl_unittest.cc
+++ b/content/browser/font_access/font_access_manager_impl_unittest.cc
@@ -279,6 +279,24 @@
       }));
   run_loop.Run();
 }
+
+TEST_F(FontAccessManagerImplTest, FontAccessContextFindAllFontsTest) {
+  FontAccessContext* font_access_context =
+      static_cast<FontAccessContext*>(manager_.get());
+
+  base::RunLoop run_loop;
+  font_access_context->FindAllFonts(base::BindLambdaForTesting(
+      [&](FontEnumerationStatus status,
+          std::vector<blink::mojom::FontMetadata> fonts) {
+        EXPECT_EQ(status, FontEnumerationStatus::kOk)
+            << "Enumeration expected to be successful.";
+        EXPECT_GT(fonts.size(), 0u)
+            << "Enumeration expected to yield at least 1 font";
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
 #endif
 
 }  // namespace content
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 44d35de..45b65d4 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -34,6 +34,7 @@
 #include "cc/input/input_handler.h"
 #include "cc/layers/layer.h"
 #include "cc/metrics/begin_main_frame_metrics.h"
+#include "cc/metrics/web_vital_metrics.h"
 #include "cc/mojo_embedder/async_layer_tree_frame_sink.h"
 #include "cc/resources/ui_resource_manager.h"
 #include "cc/trees/layer_tree_host.h"
@@ -709,6 +710,10 @@
   return nullptr;
 }
 
+std::unique_ptr<cc::WebVitalMetrics> CompositorImpl::GetWebVitalMetrics() {
+  return nullptr;
+}
+
 std::unique_ptr<ui::WindowAndroidCompositor::ReadbackRef>
 CompositorImpl::TakeReadbackRef() {
   ++pending_readbacks_;
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 747b97b6..1d45493 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -144,6 +144,7 @@
       cc::ActiveFrameSequenceTrackers trackers) override {}
   std::unique_ptr<cc::BeginMainFrameMetrics> GetBeginMainFrameMetrics()
       override;
+  std::unique_ptr<cc::WebVitalMetrics> GetWebVitalMetrics() override;
   void NotifyThroughputTrackerResults(
       cc::CustomTrackerResults results) override {}
   void DidObserveFirstScrollDelay(
diff --git a/content/browser/renderer_host/navigation_controller_impl_browsertest.cc b/content/browser/renderer_host/navigation_controller_impl_browsertest.cc
index d00e45ae..fc3a55a 100644
--- a/content/browser/renderer_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/renderer_host/navigation_controller_impl_browsertest.cc
@@ -10896,10 +10896,19 @@
 // 1) initiated by a cross-site frame
 // 2) same-document
 // 3) to a http URL with port 0.
-// This is the scenario behind https://crbug.com/1065532.
-// TODO(crbug.com/1138540): Flakes.
+//
+// The history: before https://crbug.com/1065532 was fixed, this was a browser
+// crash; afterwards, but before crbug.com/1136678 was fixed, it led to a
+// renderer kill; now, it should just be a failed navigation (assuming port 0 is
+// unreachable).
 IN_PROC_BROWSER_TEST_P(NavigationControllerBrowserTest,
-                       DISABLED_SameDocumentNavigationToHttpPortZero) {
+                       SameDocumentNavigationToHttpPortZero) {
+  // The test server doesn't support port 0 (and, more generally, serving files
+  // from a specific port), so we add a URLLoaderInterceptor that will provide
+  // a response to our requests to port 0 later on.
+  auto interceptor = URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
+      "content/test/data", GURL("http://another-site.com:0"));
+
   GURL page_url(embedded_test_server()->GetURL(
       "foo.com", "/navigation_controller/simple_page_1.html"));
   EXPECT_TRUE(NavigateToURL(shell(), page_url));
@@ -10917,7 +10926,7 @@
                                                        1);
     ASSERT_TRUE(ExecJs(
         shell(), JsReplace(kSubframeScriptTemplate, subframe_initial_url)));
-    subframe_injection_observer.WaitForNavigationFinished();
+    subframe_injection_observer.Wait();
     ASSERT_TRUE(subframe_injection_observer.last_navigation_succeeded());
   }
 
@@ -10925,63 +10934,28 @@
   // http URL that has port=0.  Note that this is valid port according to the
   // URL spec (https://url.spec.whatwg.org/#port-state).
   //
-  // Before the fix for SchemeHostPort's handling of port=0 the 2nd iteration
-  // below would produce a browser process CHECK/crash:
-  // - Iteration #1: The navigation will error out (because nothing is listening
-  //   on port zero in the test case).  Since
-  //   RenderFrameHostImpl::FailedNavigation doesn't call
-  //   GetOriginForURLLoaderFactory, this iteration wouldn't trigger a crash.
-  // - Iteration #2: The navigation will be treated as a same-document
-  //   navigation, because |old_url| and |new_url| will be considered the same
-  //   when inspected by GetNavigationType in navigation_controller_impl.cc.
-  //   This will trigger a call to GetOriginForURLLoaderFactory which will
-  //   crash if port=0 confuses url::Origin::Resolve.  It is unclear if treating
-  //   the 2nd iteration as same-document should be considered a bug (see also
-  //   https://crbug.com/1065532#c4).
-  //
-  // After the fix for SchemeHostPort's handling of port=0, both iterations
-  // would trigger a CANNOT_COMMIT_ORIGIN kill (see below).
+  // Before the fix for SchemeHostPort's handling of port=0 the navigation
+  // below would produce a browser process CHECK/crash, because port 0 confused
+  // url::Origin::Resolve.
   GURL::Replacements replace_port_and_ref;
   replace_port_and_ref.SetPortStr("0");
   replace_port_and_ref.SetRefStr("someRef");
   GURL subframe_ref_url =
       subframe_initial_url.ReplaceComponents(replace_port_and_ref);
-  // Doing 2 iterations, to test the same-document navigation case as outlined
-  // in the big comment above.  This test coverage will be important if we ever
-  // fix the renderer kill that started happening after fixing SchemeHostPort's
-  // handling of port=0..
-  for (int i = 0; i < 2; i++) {
-    SCOPED_TRACE(::testing::Message() << "Navigation #" << i);
 
-    // TODO(lukasza): https://crbug.com/1065532: blink::KURL and
-    // blink::SecurityOrigin treat port=0 as an invalid port, which leads to
-    // committing an origin with port=80 rather than port=0 which leads to a
-    // CANNOT_COMMIT_ORIGIN renderer kill.  This is bad, but not as bad as a
-    // browser crash from the bug, so let's keep things this way going forward.
-    ASSERT_EQ(2u, shell()->web_contents()->GetAllFrames().size());
-    RenderProcessHostBadIpcMessageWaiter bad_ipc_waiter(
-        shell()->web_contents()->GetAllFrames()[1]->GetProcess());
+  FrameTreeNode* subframe_tree_node =
+      static_cast<WebContentsImpl*>(shell()->web_contents())
+          ->GetFrameTree()
+          ->root()
+          ->child_at(0);
+  // Shouldn't crash.
+  ASSERT_TRUE(NavigateToURLFromRenderer(subframe_tree_node, subframe_ref_url));
 
-    TestNavigationObserver subframe_ref_observer(shell()->web_contents(), 1);
-    ASSERT_TRUE(
-        ExecJs(shell(), JsReplace("document.querySelector('iframe').src = $1;",
-                                  subframe_ref_url)));
-
-#if 1
-    // TODO(lukasza): https://crbug.com/1065532: blink::KURL and
-    // blink::SecurityOrigin treat port=0 as an invalid port [...]
-    // (see the same comment above).
-    EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, bad_ipc_waiter.Wait());
-    if (!AreAllSitesIsolatedForTesting()) {
-      // Without site-per-process the main frame and the subframe are hosted in
-      // the same (killed) renderer process, which makes it difficult to
-      // continue the test after the first kill.
-      return;
-    }
-#else
-    subframe_ref_url.WaitForNavigationFinished();
-#endif
-  }
+  // As a reasonableness check, make sure we committed the right URL:
+  EXPECT_EQ(subframe_tree_node->current_frame_host()->GetLastCommittedURL(),
+            GURL("http://another-site.com:0/title2.html#someRef"));
+  EXPECT_EQ(subframe_tree_node->current_frame_host()->GetLastCommittedOrigin(),
+            url::Origin::Create(GURL("http://another-site.com:0")));
 }
 
 // Navigating a subframe to the same URL should not generate a history entry.
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 69f10d7e5..34667f7 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -3754,7 +3754,18 @@
 
 void NavigationRequest::OnRendererAbortedNavigation() {
   if (IsWaitingToCommit()) {
+    // If the NavigationRequest has already reached READY_TO_COMMIT,
+    // `render_frame_host_` owns `this`. Cache any needed state in stack
+    // variables to avoid a use-after-free.
+    FrameTreeNode* frame_tree_node = frame_tree_node_;
     render_frame_host_->NavigationRequestCancelled(this);
+    // Ensure that the speculative RFH, if any, is also cleaned up. In theory,
+    // `ResetNavigationRequest()` should handle this; however, it early-returns
+    // if there is no navigation request associated with the FrameTreeNode.
+    // Changing it to no longer early return breaks a bunch of other code that
+    // runs in `CommitPendingIfNecessary()` that expects `DidStopLoading()`
+    // won't be called if `FrameTreeNode::navigation_request()` is null...
+    frame_tree_node->render_manager()->CleanUpNavigation();
   } else {
     frame_tree_node_->navigator().CancelNavigation(frame_tree_node_);
   }
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 5db151e..9cefacd 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -3646,6 +3646,15 @@
   // the handle to this class's associated RenderWidgetHostView.
   RenderFrameHostImpl* opener_frame_host =
       FromFrameToken(GetProcess()->GetID(), opener_frame_token);
+
+  // If |opener_frame_host| has been destroyed just return.
+  // TODO(crbug.com/1150976): Get rid of having to look up the opener frame
+  // to find the newly created web contents, because it is actually just
+  // |delegate_|.
+  if (!opener_frame_host) {
+    std::move(callback).Run();
+    return;
+  }
   opener_frame_host->delegate()->ShowCreatedWindow(
       opener_frame_host, GetRenderWidgetHost()->GetRoutingID(), disposition,
       initial_rect, user_gesture);
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index ec876e1..4df92ef 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -10632,6 +10632,147 @@
   // Passes if there is no crash.
 }
 
+// Tests what happens if the renderer attempts to cancel a navigation after the
+// NavigationRequest has already reached READY_TO_COMMIT.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
+                       CancelNavigationAfterReadyToCommit) {
+  class NavigationCanceller : public WebContentsObserver {
+   public:
+    NavigationCanceller(WebContents* web_contents,
+                        RenderFrameHost& requesting_rfh)
+        : WebContentsObserver(web_contents), requesting_rfh_(requesting_rfh) {}
+
+    // WebContentsObserver overrides:
+    void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
+      // Cancel the navigation in the renderer, but don't wait for the
+      // reply. This is to ensure the browser process does not process any
+      // incoming messages and learn about the renderer's cancellation
+      // before the browser process dispatches a CommitNavigation() to the
+      // renderer.
+      ExecuteScriptAsync(&requesting_rfh_, "window.stop()");
+    }
+
+   private:
+    RenderFrameHost& requesting_rfh_;
+  };
+
+  // Set up a test page with a same-site child frame.
+  // TODO(dcheng): In the future, it might be useful to also have a test where
+  // the child frame is same-site but cross-origin, and have the parent
+  // initiate the navigation in the child frame.
+  GURL url1(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(a)"));
+  EXPECT_TRUE(NavigateToURL(web_contents(), url1));
+
+  // Now navigate the first child to another same-site page. Note that with
+  // subframe RenderDocument, this will create a speculative RFH.
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  TestNavigationManager nav_manager(web_contents(), url2);
+  FrameTreeNode* first_child = root->child_at(0);
+  EXPECT_TRUE(BeginNavigateToURLFromRenderer(
+      first_child->render_manager()->current_frame_host(), url2));
+
+  EXPECT_TRUE(nav_manager.WaitForResponse());
+
+  bool using_speculative_rfh =
+      !!first_child->render_manager()->speculative_frame_host();
+
+  NavigationCanceller canceller(
+      web_contents(), *first_child->render_manager()->current_frame_host());
+
+  nav_manager.WaitForNavigationFinished();
+  // The navigation should be committed if and only if it committed in a new
+  // RFH (i.e. if the navigation used a speculative RFH).
+  EXPECT_EQ(using_speculative_rfh, nav_manager.was_committed());
+}
+
+// Test that triggering fallback handling for <object> with an HTTP error does
+// not result in the renderer ignoring a `CommitNavigation()` IPC.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
+                       CommitNavigationWithHTTPErrorInObjectTag) {
+  // TODO(https://crbug.com/1151424): This triggers a DCHECK() since the
+  // fallback path expects the frame (which is still provisional) to already be
+  // swapped in.
+  if (GetRenderDocumentLevel() == RenderDocumentLevel::kSubframe)
+    return;
+
+  // Set up a test page with a same-site child frame hosted in an <object> tag.
+  // TODO(dcheng): In the future, it might be useful to also have a test where
+  // the child frame is same-site but cross-origin, and have the parent
+  // initiate the navigation in the child frame.
+  GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents(), url1));
+
+  // There should be one nested browsing context.
+  EXPECT_EQ(1, EvalJs(web_contents(), "window.length"));
+  // And there should be no fallback content displayed.
+  EXPECT_EQ("", EvalJs(web_contents(), "document.body.innerText"));
+
+  // Now navigate the first child to another same-site page that will result in
+  // a 404. Note that with subframe RenderDocument, this will create a
+  // speculative RFH.
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  GURL url2(embedded_test_server()->GetURL("a.com", "/page404.html"));
+  TestNavigationManager nav_manager(web_contents(), url2);
+  FrameTreeNode* first_child = root->child_at(0);
+  EXPECT_TRUE(BeginNavigateToURLFromRenderer(
+      first_child->render_manager()->current_frame_host(), url2));
+
+  nav_manager.WaitForNavigationFinished();
+  // Though this test triggers fallback handling, the navigation itself should
+  // still be committed.
+  EXPECT_TRUE(nav_manager.was_committed());
+  // Despite the fact that this is an HTTP error, it still commits as a regular
+  // navigation and will be considered successful.
+  EXPECT_TRUE(nav_manager.was_successful());
+
+  // Note: Chrome is not compliant with the spec. An HTTP error triggers
+  // fallback content, which is supposed to discard the nested browsing
+  // context...
+  EXPECT_EQ(1, EvalJs(web_contents(), "window.length"));
+  EXPECT_EQ("fallback", EvalJs(web_contents(), "document.body.innerText"));
+}
+
+// Test that triggering fallback handling for <object> with a network error that
+// is routed via a `CommitFailedNavigation()` IPC ends up being ignored.
+IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest,
+                       CommitFailedNavigationInObjectTag) {
+  // Set up a test page with a same-site child frame hosted in an <object> tag.
+  GURL url1(embedded_test_server()->GetURL("a.com", "/object-frame.html"));
+  EXPECT_TRUE(NavigateToURL(web_contents(), url1));
+
+  // Now navigate the first child to another same-site page that will result in
+  // a network error. Note that with subframe RenderDocument, this will create a
+  // speculative RFH.
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  GURL error_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
+  std::unique_ptr<URLLoaderInterceptor> interceptor =
+      URLLoaderInterceptor::SetupRequestFailForURL(error_url,
+                                                   net::ERR_CONNECTION_REFUSED);
+  TestNavigationManager nav_manager(web_contents(), error_url);
+  FrameTreeNode* first_child = root->child_at(0);
+  EXPECT_TRUE(BeginNavigateToURLFromRenderer(
+      first_child->render_manager()->current_frame_host(), error_url));
+
+  // Note: this needs to be checked before `WaitForResponse()`, as the
+  // `CommitFailedNavigation()` IPC is sent somewhere inside
+  // `WaitForResponse()`.
+  bool using_speculative_rfh =
+      !!first_child->render_manager()->speculative_frame_host();
+  EXPECT_EQ(using_speculative_rfh,
+            GetRenderDocumentLevel() == RenderDocumentLevel::kSubframe);
+
+  // Returns false the test server has already been shutdown.
+  EXPECT_FALSE(nav_manager.WaitForResponse());
+
+  nav_manager.WaitForNavigationFinished();
+  EXPECT_FALSE(nav_manager.was_committed());
+
+  // Make sure that the speculative RFH has been cleaned up, if needed.
+  EXPECT_EQ(nullptr, first_child->render_manager()->speculative_frame_host());
+}
+
 #if defined(OS_ANDROID)
 
 namespace {
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index a2671da..834303a 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -75,6 +75,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/dom_storage_context.h"
+#include "content/public/browser/font_access_context.h"
 #include "content/public/browser/login_delegate.h"
 #include "content/public/browser/native_file_system_entry_factory.h"
 #include "content/public/browser/network_service_instance.h"
@@ -1445,6 +1446,11 @@
   return filesystem_context_.get();
 }
 
+FontAccessContext* StoragePartitionImpl::GetFontAccessContext() {
+  DCHECK(initialized_);
+  return font_access_manager_.get();
+}
+
 storage::DatabaseTracker* StoragePartitionImpl::GetDatabaseTracker() {
   DCHECK(initialized_);
   return database_tracker_.get();
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index c8ab7ab..ff10638 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -73,6 +73,7 @@
 class BlobRegistryWrapper;
 class ConversionManagerImpl;
 class CookieStoreContext;
+class FontAccessContext;
 class GeneratedCodeCacheContext;
 class IndexedDBContextImpl;
 class NativeFileSystemEntryFactory;
@@ -139,6 +140,7 @@
   ChromeAppCacheService* GetAppCacheService() override;
   BackgroundSyncContextImpl* GetBackgroundSyncContext() override;
   storage::FileSystemContext* GetFileSystemContext() override;
+  FontAccessContext* GetFontAccessContext() override;
   storage::DatabaseTracker* GetDatabaseTracker() override;
   DOMStorageContextWrapper* GetDOMStorageContext() override;
   LockManager* GetLockManager();  // override; TODO: Add to interface
diff --git a/content/child/blink_platform_impl_unittest.cc b/content/child/blink_platform_impl_unittest.cc
index 97baa5c..01baf4db 100644
--- a/content/child/blink_platform_impl_unittest.cc
+++ b/content/child/blink_platform_impl_unittest.cc
@@ -22,10 +22,9 @@
 
   base::Optional<url::Origin> checked_origin =
       url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
-          origin.Protocol().Utf8(), origin.Host().Utf8(),
-          origin.EffectivePort());
+          origin.Protocol().Utf8(), origin.Host().Utf8(), origin.Port());
   url::Origin non_checked_origin = url::Origin::CreateFromNormalizedTuple(
-      origin.Protocol().Utf8(), origin.Host().Utf8(), origin.EffectivePort());
+      origin.Protocol().Utf8(), origin.Host().Utf8(), origin.Port());
   EXPECT_EQ(checked_origin, non_checked_origin);
 }
 
@@ -95,7 +94,7 @@
             blink::WebString::FromUTF8(test.url));
     EXPECT_EQ(test.scheme, web_origin.Protocol().Utf8());
     EXPECT_EQ(test.host, web_origin.Host().Utf8());
-    EXPECT_EQ(test.port, web_origin.EffectivePort());
+    EXPECT_EQ(test.port, web_origin.Port());
 
     url::Origin url_origin = web_origin;
     EXPECT_EQ(test.scheme, url_origin.scheme());
@@ -105,7 +104,7 @@
     web_origin = url::Origin::Create(GURL(test.url));
     EXPECT_EQ(test.scheme, web_origin.Protocol().Utf8());
     EXPECT_EQ(test.host, web_origin.Host().Utf8());
-    EXPECT_EQ(test.port, web_origin.EffectivePort());
+    EXPECT_EQ(test.port, web_origin.Port());
 
     CheckCastedOriginsAlreadyNormalized(web_origin);
   }
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index cda85fd..572e75a 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -207,7 +207,6 @@
     "java/src/org/chromium/content/browser/androidoverlay/AndroidOverlayProviderImpl.java",
     "java/src/org/chromium/content/browser/androidoverlay/DialogOverlayCore.java",
     "java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java",
-    "java/src/org/chromium/content/browser/androidoverlay/ThreadHoppingHost.java",
     "java/src/org/chromium/content/browser/font/AndroidFontLookupImpl.java",
     "java/src/org/chromium/content/browser/font/FontsContractWrapper.java",
     "java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/AndroidOverlayProviderImpl.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/AndroidOverlayProviderImpl.java
index c54802d..734e7e2 100644
--- a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/AndroidOverlayProviderImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/AndroidOverlayProviderImpl.java
@@ -4,9 +4,6 @@
 
 package org.chromium.content.browser.androidoverlay;
 
-import android.os.Handler;
-import android.os.HandlerThread;
-
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -32,13 +29,6 @@
     // concurrent overlays, we need to revisit this logic.
     private static final int MAX_OVERLAYS = 1;
 
-    // We maintain a thread with a Looper for the AndroidOverlays to use, since Dialog requires one.
-    // We don't want this to be the native thread that's used to create them (the browser UI thread)
-    // since we don't want to block that waiting for sync callbacks from Android, such as
-    // surfaceDestroyed.  Instead, we run all AndroidOverlays on one shared overlay-ui thread.
-    private HandlerThread mOverlayUiThread;
-    private Handler mHandler;
-
     // Number of AndroidOverlays that have been created but not released.
     private int mNumOverlays;
 
@@ -69,26 +59,14 @@
             return;
         }
 
-        startThreadIfNeeded();
         mNumOverlays++;
 
-        DialogOverlayImpl impl = new DialogOverlayImpl(
-                client, config, mHandler, mNotifyReleasedRunnable, false /* asPanel*/);
+        DialogOverlayImpl impl =
+                new DialogOverlayImpl(client, config, mNotifyReleasedRunnable, false /* asPanel*/);
         DialogOverlayImpl.MANAGER.bind(impl, request);
     }
 
     /**
-     * Make sure that mOverlayUiThread and mHandler are ready for use, if needed.
-     */
-    private void startThreadIfNeeded() {
-        if (mOverlayUiThread != null) return;
-
-        mOverlayUiThread = new HandlerThread("AndroidOverlayThread");
-        mOverlayUiThread.start();
-        mHandler = new Handler(mOverlayUiThread.getLooper());
-    }
-
-    /**
      * Called by AndroidOverlays when they no longer need the thread via |mNotifyReleasedRunnable|.
      */
     private void notifyReleased() {
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayCore.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayCore.java
index 19185d6..efe2bc4 100644
--- a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayCore.java
@@ -7,9 +7,7 @@
 import android.annotation.SuppressLint;
 import android.app.Dialog;
 import android.content.Context;
-import android.os.Build;
 import android.os.IBinder;
-import android.os.Looper;
 import android.view.Gravity;
 import android.view.Surface;
 import android.view.SurfaceHolder;
@@ -17,16 +15,15 @@
 import android.view.WindowManager;
 
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.gfx.mojom.Rect;
 import org.chromium.media.mojom.AndroidOverlayConfig;
 
 /**
  * Core class for control of a single Dialog-based AndroidOverlay instance.  Everything runs on the
- * overlay thread, which is not the Browser UI thread.
+ * Browser UI thread.
  *
- * Note that this does not implement AndroidOverlay; we assume that, and the associated thread-
- * hopping, is handled elsewhere (DialogOverlayImpl).
+ * Note that this does not implement AndroidOverlay; we just manage the android side of it.  The
+ * mojo interface is implemented by DialogOverlayImpl.
  */
 class DialogOverlayCore {
     private static final String TAG = "DSCore";
@@ -36,16 +33,9 @@
         // Notify the host that we have a surface.
         void onSurfaceReady(Surface surface);
 
-        // Notify the host that we have failed to get a surface or the surface was destroyed.
+        // Notify the host that we have failed to get a surface or the surface was destroyed.  This
+        // must synchronously stop using the surface we've provided, if any.
         void onOverlayDestroyed();
-
-        // Wait until the host has been told to close.  We are allowed to let surfaceDestroyed
-        // proceed once this happens.
-        void waitForClose();
-
-        // Notify the host that waitForClose() is done waiting, so that it can enforce that cleanup
-        // always happens.
-        void enforceClose();
     }
 
     private Host mHost;
@@ -64,9 +54,6 @@
     // If true, then we'll be a panel rather than media overlay.  This is for testing.
     private boolean mAsPanel;
 
-    // Looper that we're initialized on, for thread checking.
-    private Looper mLooper;
-
     /**
      * Construction may be called from a random thread, for simplicity.  Call initialize from the
      * proper thread before doing anything else.
@@ -82,7 +69,6 @@
      */
     public void initialize(
             Context context, AndroidOverlayConfig config, Host host, boolean asPanel) {
-        mLooper = Looper.myLooper();
         mHost = host;
         mAsPanel = asPanel;
 
@@ -99,8 +85,6 @@
      * the client releasing the AndroidOverlay.  This may be called more than once.
      */
     public void release() {
-        assertProperThread();
-
         // If we've not released the dialog yet, then do so.
         dismissDialogQuietly();
 
@@ -153,8 +137,6 @@
 
         @Override
         public void surfaceCreated(SurfaceHolder holder) {
-            assertProperThread();
-
             // Make sure that we haven't torn down the dialog yet.
             if (mDialog == null) return;
 
@@ -165,28 +147,8 @@
         public void surfaceDestroyed(SurfaceHolder holder) {
             if (mDialog == null || mHost == null) return;
 
-            // This method should be called on the overlay thread, but is sometimes called on the
-            // browser UI thread due to framework bugs.  If that happens, we can't really clean up
-            // properly (synchronously), since |mHost| can't be closed properly from the remote side
-            // since we're blocking the UI thread.  To avoid that, we just give up on synchronous
-            // shutdown and hope for the best.
-            //
-            // We only allow it on P and later, since it's observed on both P and Q.
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
-                assertProperThread();
-            } else if (mLooper != Looper.myLooper()) {
-                Log.e(TAG, "surfaceDestroyed called on wrong thread.  Avoiding proper shutdown.");
-                // We still notify the client, so that it can shut down, but we don't wait.  Note
-                // that this can result in calls back into |this| on the overlay thread, including
-                // clearing |mHost| even if it is not null now.
-                mHost.onOverlayDestroyed();
-                return;
-            }
-
             // Notify the host that we've been destroyed, and wait for it to clean up or time out.
             mHost.onOverlayDestroyed();
-            mHost.waitForClose();
-            mHost.enforceClose();
             mHost = null;
         }
 
@@ -195,8 +157,6 @@
     }
 
     public void onWindowToken(IBinder token) {
-        assertProperThread();
-
         if (mDialog == null || mHost == null) return;
 
         if (token == null || (mLayoutParams.token != null && token != mLayoutParams.token)) {
@@ -286,7 +246,9 @@
 
     // Dismiss |mDialog| if needed, and clear it and the callbacks.  This hides any exception, since
     // there's a race during app shutdown between this running on the overlay-ui thread, and losing
-    // the window token on the browser UI thread.
+    // the window token on the browser UI thread.  Now that we run on the browser UI thread, it's
+    // likely that this race no longer exists.  Since it's hard to repro locally, and because this
+    // doesn't hurt, the try / catch is still here.
     // See crbug.com/784224 .
     private void dismissDialogQuietly() {
         if (mDialog != null && mDialog.isShowing()) {
@@ -300,16 +262,4 @@
         mDialog = null;
         mDialogCallbacks = null;
     }
-
-    // Throw an exception if we're on the wrong thread, regardless of build.
-    private void assertProperThread() {
-        if (mLooper == Looper.myLooper()) return;
-
-        // Special-case the browser UI thread, just to be more helpful.
-        if (ThreadUtils.runningOnUiThread()) {
-            throw new RuntimeException("DialogOverlayCore is on the UI thread");
-        } else {
-            throw new RuntimeException("DialogOverlayCore is on the wrong thread");
-        }
-    }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
index 3414be8e0..d6a0b02 100644
--- a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
@@ -5,7 +5,6 @@
 package org.chromium.content.browser.androidoverlay;
 
 import android.content.Context;
-import android.os.Handler;
 import android.os.IBinder;
 import android.view.Surface;
 
@@ -14,12 +13,11 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.task.PostTask;
-import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.gfx.mojom.Rect;
 import org.chromium.media.mojom.AndroidOverlay;
 import org.chromium.media.mojom.AndroidOverlayClient;
 import org.chromium.media.mojom.AndroidOverlayConfig;
+import org.chromium.mojo.system.MessagePipeHandle;
 import org.chromium.mojo.system.MojoException;
 
 /**
@@ -32,16 +30,9 @@
     private static final String TAG = "DialogOverlayImpl";
 
     private AndroidOverlayClient mClient;
-    private Handler mOverlayHandler;
     // Runnable that we'll run when the overlay notifies us that it's been released.
     private Runnable mReleasedRunnable;
 
-    // Runnable that will release |mDialogCore| when posted to mOverlayHandler.  We keep this
-    // separately from mDialogCore itself so that we can call it after we've discarded the latter.
-    private Runnable mReleaseCoreRunnable;
-
-    private final ThreadHoppingHost mHoppingHost;
-
     private DialogOverlayCore mDialogCore;
 
     private long mNativeHandle;
@@ -58,21 +49,17 @@
     /**
      * @param client Mojo client interface.
      * @param config initial overlay configuration.
-     * @param handler handler that posts to the overlay thread.  This is the android UI thread that
-     * the dialog uses, not the browser UI thread.
      * @param provider the overlay provider that owns us.
      * @param asPanel the overlay should be a panel, above the compositor.  This is for testing.
      */
     public DialogOverlayImpl(AndroidOverlayClient client, final AndroidOverlayConfig config,
-            Handler overlayHandler, Runnable releasedRunnable, final boolean asPanel) {
+            Runnable releasedRunnable, final boolean asPanel) {
         ThreadUtils.assertOnUiThread();
 
         mClient = client;
         mReleasedRunnable = releasedRunnable;
-        mOverlayHandler = overlayHandler;
 
         mDialogCore = new DialogOverlayCore();
-        mHoppingHost = new ThreadHoppingHost(this);
 
         // Register to get token updates.  Note that this may not call us back directly, since
         // |mDialogCore| hasn't been initialized yet.
@@ -80,39 +67,17 @@
                 config.routingToken.high, config.routingToken.low, config.powerEfficient);
 
         if (mNativeHandle == 0) {
-            mClient.onDestroyed();
+            notifyDestroyed();
             cleanup();
             return;
         }
 
-        // Post init to the overlay thread.
         final DialogOverlayCore dialogCore = mDialogCore;
         final Context context = ContextUtils.getApplicationContext();
         DialogOverlayImplJni.get().getCompositorOffset(
                 mNativeHandle, DialogOverlayImpl.this, config.rect);
-        mOverlayHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                dialogCore.initialize(context, config, mHoppingHost, asPanel);
-                // Now that |mDialogCore| has been initialized, we are ready for token callbacks.
-                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mNativeHandle != 0) {
-                            DialogOverlayImplJni.get().completeInit(
-                                    mNativeHandle, DialogOverlayImpl.this);
-                        }
-                    }
-                });
-            }
-        });
-
-        mReleaseCoreRunnable = new Runnable() {
-            @Override
-            public void run() {
-                dialogCore.release();
-            }
-        };
+        dialogCore.initialize(context, config, this, asPanel);
+        DialogOverlayImplJni.get().completeInit(mNativeHandle, DialogOverlayImpl.this);
     }
 
     // AndroidOverlay impl.
@@ -128,13 +93,9 @@
         // TODO(liberato): verify that this actually works, else add an explicit shutdown and hope
         // that the client calls it.
 
-        // Allow surfaceDestroyed to proceed, if it's waiting.
-        mHoppingHost.onClose();
-
         // Notify |mDialogCore| that it has been released.
-        if (mReleaseCoreRunnable != null) {
-            mOverlayHandler.post(mReleaseCoreRunnable);
-            mReleaseCoreRunnable = null;
+        if (mDialogCore != null) {
+            mDialogCore.release();
 
             // Note that we might get messagaes from |mDialogCore| after this, since they might be
             // dispatched before |r| arrives.  Clearing |mDialogCore| causes us to ignore them.
@@ -166,14 +127,7 @@
 
         // |rect| is relative to the compositor surface.  Convert it to be relative to the screen.
         DialogOverlayImplJni.get().getCompositorOffset(mNativeHandle, DialogOverlayImpl.this, rect);
-
-        final DialogOverlayCore dialogCore = mDialogCore;
-        mOverlayHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                dialogCore.layoutSurface(rect);
-            }
-        });
+        mDialogCore.layoutSurface(rect);
     }
 
     // Receive the compositor offset, as part of scheduleLayout.  Adjust the layout position.
@@ -203,49 +157,15 @@
         if (mDialogCore == null) return;
 
         // Notify the client that the overlay is gone.
-        if (mClient != null) mClient.onDestroyed();
+        notifyDestroyed();
 
-        // Also clear out |mDialogCore| to prevent us from sending useless messages to it.  Note
-        // that we might have already sent useless messages to it, and it should be robust against
-        // that sort of thing.
+        // Also clear out |mDialogCore| to prevent us from sending useless messages to it.
         cleanup();
 
         // Note that we don't notify |mReleasedRunnable| yet, though we could.  We wait for the
         // client to close their connection first.
     }
 
-    // DialogOverlayCore.Host impl.
-    // Due to threading issues, |mHoppingHost| doesn't forward this.
-    @Override
-    public void waitForClose() {
-        assert false : "Not reached";
-    }
-
-    // DialogOverlayCore.Host impl
-    @Override
-    public void enforceClose() {
-        // Pretend that the client closed us, even if they didn't.  It's okay if this is called more
-        // than once.  The client might have already called it, or might call it later.
-        close();
-    }
-
-    /**
-     * Send |token| to the |mDialogCore| on the overlay thread.
-     */
-    private void sendWindowTokenToCore(final IBinder token) {
-        ThreadUtils.assertOnUiThread();
-
-        if (mDialogCore != null) {
-            final DialogOverlayCore dialogCore = mDialogCore;
-            mOverlayHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    dialogCore.onWindowToken(token);
-                }
-            });
-        }
-    }
-
     /**
      * Callback from native that the window token has changed.
      */
@@ -260,7 +180,7 @@
         // skipping sending null if we haven't sent any non-null token yet.  If we're transitioning
         // between windows, that might make the client's job easier. It wouldn't have to guess when
         // a new token is available.
-        sendWindowTokenToCore(token);
+        mDialogCore.onWindowToken(token);
     }
 
     /**
@@ -271,10 +191,10 @@
         ThreadUtils.assertOnUiThread();
 
         // Notify the client that the overlay is going away.
-        if (mClient != null) mClient.onDestroyed();
+        notifyDestroyed();
 
         // Notify |mDialogCore| that it lost the token, if it had one.
-        sendWindowTokenToCore(null);
+        if (mDialogCore != null) mDialogCore.onWindowToken(null);
 
         cleanup();
     }
@@ -319,10 +239,32 @@
         mClient = null;
     }
 
-    /**
-     * Notify the native side that we are ready for token / dismissed callbacks.  This may result in
-     * a callback before it returns.
-     */
+    private void notifyDestroyed() {
+        if (mClient == null) return;
+
+        // This is the last message to the client.
+        final AndroidOverlayClient client = mClient;
+        mClient = null;
+
+        // If we've not provided a surface, then we don't need to wait for a reply.  This happens,
+        // for example, if we fail immediately.
+        if (mSurfaceId == 0) {
+            client.onDestroyed();
+            return;
+        }
+
+        // Notify the client that the overlay is gone, synchronously.  We have to do this once we
+        // have a Surface, since we could get a surfaceDestroyed from Android at any time.  If we
+        // signal async destruction, then get surfaceDestroyed, then we're stuck.  So, clean up
+        // synchronously even if Android is not waiting for us right now.
+
+        // Don't try this at home.  It's hacky.  All of DialogOverlay is deprecated.  It will be
+        // removed once Android O is no longer supported.
+        final AndroidOverlayClient.Proxy proxy = (AndroidOverlayClient.Proxy) client;
+        final MessagePipeHandle handle = proxy.getProxyHandler().passHandle();
+        final int nativeHandle = handle.releaseNativeHandle();
+        DialogOverlayImplJni.get().notifyDestroyedSynchronously(nativeHandle);
+    }
 
     @NativeMethods
     interface Natives {
@@ -364,5 +306,13 @@
          * @param surfaceId Id that was returned by registerSurface.
          */
         Surface lookupSurfaceForTesting(int surfaceId);
+
+        /**
+         * Send a synchronous OnDestroyed message to the client.
+         * @param messagePipe Mojo message pipe ID.
+         * @param version Mojo interface version.
+         * @return none, but the message pipe is closed.
+         */
+        void notifyDestroyedSynchronously(int messagePipeHandle);
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/ThreadHoppingHost.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/ThreadHoppingHost.java
deleted file mode 100644
index 1d12ee4..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/ThreadHoppingHost.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.androidoverlay;
-
-import android.os.Handler;
-import android.view.Surface;
-
-import org.chromium.base.Log;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * DialogOverlayCore::Host implementation that transfers messages to the thread on which it was
- * constructed.  Due to threading concerns, waitForClose is not forwarded.
- */
-class ThreadHoppingHost implements DialogOverlayCore.Host {
-    private static final String TAG = "ThreadHoppingHost";
-
-    // Number of seconds we'll wait for client cleanup before we give up.  We don't want to keep
-    // onSurfaceDestroyed waiting forever.
-    private static final int CLEANUP_TIMEOUT_SECONDS = 2;
-
-    // Handler for the host we're proxying to.  Typically Browser::UI.
-    private Handler mHandler;
-
-    // Host impl that we're proxying to.
-    private final DialogOverlayCore.Host mHost;
-
-    // Semaphore to keep track of whether cleanup has started yet.
-    private final Semaphore mSemaphore = new Semaphore(0);
-
-    // We will forward to |host| on whatever thread we're constructed on.
-    public ThreadHoppingHost(DialogOverlayCore.Host host) {
-        mHandler = new Handler();
-        mHost = host;
-    }
-
-    @Override
-    public void onSurfaceReady(final Surface surface) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mHost.onSurfaceReady(surface);
-            }
-        });
-    }
-
-    @Override
-    public void onOverlayDestroyed() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mHost.onOverlayDestroyed();
-            }
-        });
-    }
-
-    // We don't forward via to |mHandler|, since it should be asynchronous.  Else, the |mHandler|
-    // thread would block.  Instead, we wait here and somebody must call onCleanup() to let us know
-    // that cleanup has started, and that we may return.
-    @Override
-    public void waitForClose() {
-        while (true) {
-            try {
-                // TODO(liberato): in case of InterruptedException, we really should adjust the
-                // timeout to reflect remaining time.
-                if (!mSemaphore.tryAcquire(CLEANUP_TIMEOUT_SECONDS, TimeUnit.SECONDS))
-                    Log.d(TAG, "Wait for semaphore timed out.");
-                break;
-            } catch (InterruptedException e) {
-            }
-        }
-    }
-
-    @Override
-    public void enforceClose() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mHost.enforceClose();
-            }
-        });
-    }
-
-    // Notify us that cleanup has started.  This is called on |mHandler|'s thread.
-    public void onClose() {
-        mSemaphore.release(1);
-    }
-}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/androidoverlay/DialogOverlayImplTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/androidoverlay/DialogOverlayImplTestRule.java
index 495d099..d27cdf3 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/androidoverlay/DialogOverlayImplTestRule.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/androidoverlay/DialogOverlayImplTestRule.java
@@ -4,9 +4,6 @@
 
 package org.chromium.content.browser.androidoverlay;
 
-import android.os.Handler;
-import android.os.HandlerThread;
-
 import org.junit.Assert;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -28,10 +25,6 @@
  * TestRule for tests for DialogOverlayImpl.
  */
 public class DialogOverlayImplTestRule extends ContentShellActivityTestRule {
-    // overlay-ui thread.
-    private HandlerThread mOverlayUiThread;
-    private Handler mOverlayUiHandler;
-
     // Runnable that will be called on the browser UI thread when an overlay is released.
     private Runnable mReleasedRunnable;
 
@@ -52,9 +45,10 @@
         // AndroidOverlayClient
         public static final int SURFACE_READY = 0;
         public static final int DESTROYED = 1;
-        public static final int POWER_EFFICIENT = 2;
-        public static final int CLOSE = 3;
-        public static final int CONNECTION_ERROR = 4;
+        public static final int SYNCHRONOUSLY_DESTROYED = 2;
+        public static final int POWER_EFFICIENT = 3;
+        public static final int CLOSE = 4;
+        public static final int CONNECTION_ERROR = 5;
         // AndroidOverlayProviderImpl.Callbacks
         public static final int RELEASED = 100;
         // Internal to test only.
@@ -101,6 +95,12 @@
         }
 
         @Override
+        public void onSynchronouslyDestroyed(OnSynchronouslyDestroyedResponse response) {
+            mPending.add(new Event(SYNCHRONOUSLY_DESTROYED));
+            response.call();
+        }
+
+        @Override
         public void onPowerEfficientState(boolean powerEfficient) {
             mPending.add(new Event(POWER_EFFICIENT));
         }
@@ -207,11 +207,6 @@
                             }
                         });
 
-                // Set up the overlay UI thread
-                mOverlayUiThread = new HandlerThread("TestOverlayUI");
-                mOverlayUiThread.start();
-                mOverlayUiHandler = new Handler(mOverlayUiThread.getLooper());
-
                 // Just delegate to |mClient| when an overlay is released.
                 mReleasedRunnable = new Runnable() {
                     @Override
@@ -247,7 +242,7 @@
                 config.rect.height = height;
                 config.secure = mSecure;
                 DialogOverlayImpl impl = new DialogOverlayImpl(
-                        mClient, config, mOverlayUiHandler, mReleasedRunnable, true /* asPanel */);
+                        mClient, config, mReleasedRunnable, true /* asPanel */);
 
                 return impl;
             }
diff --git a/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java b/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java
index a97ba04..2bdfed0 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java
@@ -134,8 +134,6 @@
     void checkOverlayDidntCall() {
         assertEquals(null, mHost.surface());
         assertEquals(0, mHost.destroyedCount());
-        assertEquals(0, mHost.waitCloseCount());
-        assertEquals(0, mHost.enforceCloseCount());
     }
 
     // Return the SurfaceHolder callback that was provided to takeSurface(), if any.
@@ -158,8 +156,6 @@
     class HostMock implements DialogOverlayCore.Host {
         private Surface mSurface;
         private int mDestroyedCount;
-        private int mWaitCloseCount;
-        private int mEnforceCloseCount;
 
         @Override
         public void onSurfaceReady(Surface surface) {
@@ -171,16 +167,6 @@
             mDestroyedCount++;
         }
 
-        @Override
-        public void waitForClose() {
-            mWaitCloseCount++;
-        }
-
-        @Override
-        public void enforceClose() {
-            mEnforceCloseCount++;
-        }
-
         public Surface surface() {
             return mSurface;
         }
@@ -188,14 +174,6 @@
         public int destroyedCount() {
             return mDestroyedCount;
         }
-
-        public int waitCloseCount() {
-            return mWaitCloseCount;
-        }
-
-        public int enforceCloseCount() {
-            return mEnforceCloseCount;
-        }
     };
 
     HostMock mHost = new HostMock();
@@ -271,8 +249,6 @@
 
         mCore.release();
         assertEquals(0, mHost.destroyedCount());
-        assertEquals(0, mHost.waitCloseCount());
-        assertEquals(0, mHost.enforceCloseCount());
         checkDialogIsNotShown();
     }
 
@@ -286,11 +262,7 @@
         // Destroy the surface.
         holderCallback().surfaceDestroyed(mHolder);
         // |mCore| should have waited for cleanup during surfaceDestroyed.
-        assertEquals(1, mHost.waitCloseCount());
-        // Since we waited for cleanup, also pretend that the release was posted during the wait and
-        // will arrive after the wait completes.
         mCore.release();
-        assertEquals(1, mHost.enforceCloseCount());
 
         checkOverlayWasDestroyed();
     }
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 3ab7a86..8132c83 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -153,6 +153,7 @@
     "file_select_listener.h",
     "file_url_loader.h",
     "focused_node_details.h",
+    "font_access_context.h",
     "font_list_async.h",
     "frame_accept_header.cc",
     "frame_accept_header.h",
diff --git a/content/public/browser/font_access_context.h b/content/public/browser/font_access_context.h
new file mode 100644
index 0000000..42e4ea3
--- /dev/null
+++ b/content/public/browser/font_access_context.h
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_FONT_ACCESS_CONTEXT_H_
+#define CONTENT_PUBLIC_BROWSER_FONT_ACCESS_CONTEXT_H_
+
+#include "base/callback_forward.h"
+#include "build/build_config.h"
+#include "content/common/content_export.h"
+#include "third_party/blink/public/mojom/font_access/font_access.mojom.h"
+
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
+    defined(OS_MAC)
+#define PLATFORM_HAS_LOCAL_FONT_ENUMERATION_IMPL 1
+#endif
+
+namespace content {
+
+class CONTENT_EXPORT FontAccessContext {
+ public:
+  using FindAllFontsCallback =
+      base::OnceCallback<void(blink::mojom::FontEnumerationStatus,
+                              std::vector<blink::mojom::FontMetadata>)>;
+  virtual void FindAllFonts(FindAllFontsCallback callback) = 0;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_FONT_ACCESS_CONTEXT_H_
diff --git a/content/public/browser/gpu_utils.cc b/content/public/browser/gpu_utils.cc
index f97df834..240407a 100644
--- a/content/public/browser/gpu_utils.cc
+++ b/content/public/browser/gpu_utils.cc
@@ -120,11 +120,28 @@
   gpu_preferences.enable_native_gpu_memory_buffers =
       command_line->HasSwitch(switches::kEnableNativeGpuMemoryBuffers);
 
-#if defined(OS_CHROMEOS)
-  gpu_preferences.platform_disallows_chromeos_direct_video_decoder =
-      command_line->HasSwitch(
-          switches::kPlatformDisallowsChromeOSDirectVideoDecoder);
-#endif
+#if BUILDFLAG(IS_ASH)
+#if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
+  // The direct VideoDecoder is disallowed on some particular SoC/platforms.
+  const bool should_use_direct_video_decoder =
+      !command_line->HasSwitch(
+          switches::kPlatformDisallowsChromeOSDirectVideoDecoder) &&
+      base::FeatureList::IsEnabled(media::kUseChromeOSDirectVideoDecoder);
+
+  // For testing purposes, the following flag allows using the "other" video
+  // decoder implementation.
+  if (base::FeatureList::IsEnabled(
+          media::kUseAlternateVideoDecoderImplementation)) {
+    gpu_preferences.enable_chromeos_direct_video_decoder =
+        !should_use_direct_video_decoder;
+  } else {
+    gpu_preferences.enable_chromeos_direct_video_decoder =
+        should_use_direct_video_decoder;
+  }
+#else   // !BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
+  gpu_preferences.enable_chromeos_direct_video_decoder = false;
+#endif  // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
+#endif  // BUILDFLAG(IS_ASH)
 
 #if defined(OS_ANDROID)
   gpu_preferences.disable_oopr_debug_crash_dump =
diff --git a/content/public/browser/storage_partition.h b/content/public/browser/storage_partition.h
index 04c7304..0fd510e 100644
--- a/content/public/browser/storage_partition.h
+++ b/content/public/browser/storage_partition.h
@@ -62,6 +62,7 @@
 class DedicatedWorkerService;
 class DevToolsBackgroundServicesContext;
 class DOMStorageContext;
+class FontAccessContext;
 class GeneratedCodeCacheContext;
 class NativeFileSystemEntryFactory;
 class PlatformNotificationContext;
@@ -121,6 +122,7 @@
   virtual AppCacheService* GetAppCacheService() = 0;
   virtual BackgroundSyncContext* GetBackgroundSyncContext() = 0;
   virtual storage::FileSystemContext* GetFileSystemContext() = 0;
+  virtual FontAccessContext* GetFontAccessContext() = 0;
   virtual storage::DatabaseTracker* GetDatabaseTracker() = 0;
   virtual DOMStorageContext* GetDOMStorageContext() = 0;
   virtual storage::mojom::IndexedDBControl& GetIndexedDBControl() = 0;
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 655f297..351dd03 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -596,7 +596,7 @@
                                const GURL& expected_commit_url) {
   RenderFrameHost* rfh = adapter.render_frame_host();
   TestFrameNavigationObserver nav_observer(rfh);
-  if (!ExecJs(rfh, JsReplace("location = $1", url)))
+  if (!BeginNavigateToURLFromRenderer(adapter, url))
     return false;
   nav_observer.Wait();
   return nav_observer.last_committed_url() == expected_commit_url &&
@@ -616,6 +616,11 @@
   return nav_observer.last_committed_url() == url;
 }
 
+bool BeginNavigateToURLFromRenderer(const ToRenderFrameHost& adapter,
+                                    const GURL& url) {
+  return ExecJs(adapter, JsReplace("location = $1", url));
+}
+
 bool NavigateIframeToURL(WebContents* web_contents,
                          const std::string& iframe_id,
                          const GURL& url) {
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index c1c5d88..6a040db 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -157,6 +157,14 @@
     const ToRenderFrameHost& adapter,
     const GURL& url);
 
+// Perform a renderer-initiated navigation of |window| to |url|. Unlike the
+// previous set of helpers, does not block. The navigation is done by assigning
+// location.href in the frame |adapter|. Returns the result of executing the IPC
+// to evaluate the JS that assigns location.href.
+WARN_UNUSED_RESULT bool BeginNavigateToURLFromRenderer(
+    const ToRenderFrameHost& adapter,
+    const GURL& url);
+
 // Navigate a frame with ID |iframe_id| to |url|, blocking until the navigation
 // finishes.  Uses a renderer-initiated navigation from script code in the
 // main frame.
diff --git a/content/public/test/test_storage_partition.cc b/content/public/test/test_storage_partition.cc
index b68d3ab..686e2922 100644
--- a/content/public/test/test_storage_partition.cc
+++ b/content/public/test/test_storage_partition.cc
@@ -84,6 +84,10 @@
   return nullptr;
 }
 
+FontAccessContext* TestStoragePartition::GetFontAccessContext() {
+  return nullptr;
+}
+
 ServiceWorkerContext* TestStoragePartition::GetServiceWorkerContext() {
   return service_worker_context_;
 }
diff --git a/content/public/test/test_storage_partition.h b/content/public/test/test_storage_partition.h
index f183057e..358b064b 100644
--- a/content/public/test/test_storage_partition.h
+++ b/content/public/test/test_storage_partition.h
@@ -85,6 +85,8 @@
   }
   storage::FileSystemContext* GetFileSystemContext() override;
 
+  FontAccessContext* GetFontAccessContext() override;
+
   void set_background_sync_context(BackgroundSyncContext* context) {
     background_sync_context_ = context;
   }
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_i