diff --git a/DEPS b/DEPS
index 490d5e1..6ffff95f 100644
--- a/DEPS
+++ b/DEPS
@@ -204,7 +204,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'f326e4faef66d529904a7b53eb931fd576c82887',
+  'skia_revision': 'f3b959c000718c55800fd5232cdff6a650478316',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -216,7 +216,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '251ba5cb119ff2fed0e861cbc9b096c45004c1fa',
+  'angle_revision': 'cf023b3a8cd1fdeba958849517eee411023c6ae8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -255,7 +255,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': '8cc4d0dc32a1f8734e77266bb6abdd11f18a631c',
+  'freetype_revision': '9f94d8533cefa8a023a0b81633032fe0aaea08fb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -279,7 +279,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': '48576261660b05a86df4b9b079ad81e474b8786c',
+  'devtools_frontend_revision': 'b36ad7e1d1112e8aeceb083def86497bbf140ffc',
   # 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.
@@ -567,7 +567,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '0118f4a52f8cd03bb95f888a57e064f01096ff2e',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '1d2349b616ed4d24b5a66aadbfeb11d52849d9a1',
       'condition': 'checkout_ios',
   },
 
@@ -923,7 +923,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e602c60a2b59e4a74033fe7580e50d9de9f5753e',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '55e9ddf21994663b4564ae7fff33238e84a5ba23',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1295,7 +1295,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0fdb2247e82e7f60e5391c674ce7267019a5692d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'c56ea91f6bd1b910be1ba373b950c04f72595bd9',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1545,7 +1545,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '3c2fe3888658d82b47ca831d59a2e07579619c2d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '6c80aebd00360ee00c84c7b14422c5fe475d8916',
+    Var('webrtc_git') + '/src.git' + '@' + '393159147685c9294e423eb8b2bdc92dc78acfd4',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1617,7 +1617,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@be1d125a8b0666e93878cd2501f6f7a9f5f4f523',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@682b3e2b91af9ad3c9b2ee6057b447e210c5a015',
     'condition': 'checkout_src_internal',
   },
 
@@ -1636,7 +1636,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '9Aak3tK4oVEWWOriZzuRXEUPGWU0lUrVTTcsG4FucPgC',
+        'version': '1JjkFXZ1s86iBnYJ0jMS5E5alylmWh3-mz4jCYL3szkC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/WATCHLISTS b/WATCHLISTS
index 904dc9af..ac2a37b7 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -42,6 +42,9 @@
                   'chrome/browser/android/shortcut_.*|'\
                   'chrome/browser/android/webapps/',
     },
+    'agent_scheduling_group': {
+      'filepath': '.*agent_scheduling_group.*|.*agent_group_scheduler.*',
+    },
     'android_crash_reporting': {
       'filepath': 'chrome/android/java/src/org/chromium/chrome/browser/crash/' \
                   '|chrome/browser/android/crash/' \
@@ -2098,6 +2101,7 @@
                           'hanxi+watch@chromium.org',
                           'pkotwicz+watch@chromium.org',
                           'webapks-watchlist@chromium.org'],
+    'agent_scheduling_group': ['blink-isolation-reviews@chromium.org'],
     'android_crash_reporting': ['asvitkine+watch@chromium.org',
                                 'wnwen+watch@chromium.org'],
     'android_crazy_linker': ['johnmaguire+watch@google.com'],
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 612776d..c1c0acc 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -26,7 +26,7 @@
 
 // Enable display cutout support for Android P and above.
 const base::Feature kWebViewDisplayCutout{"WebViewDisplayCutout",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
+                                          base::FEATURE_ENABLED_BY_DEFAULT};
 
 // When enabled, passive mixed content (Audio/Video/Image subresources loaded
 // over HTTP on HTTPS sites) will be autoupgraded to HTTPS, and the load will be
diff --git a/ash/accelerometer/accelerometer_file_reader.cc b/ash/accelerometer/accelerometer_file_reader.cc
index 39e5a68..3ac8239a 100644
--- a/ash/accelerometer/accelerometer_file_reader.cc
+++ b/ash/accelerometer/accelerometer_file_reader.cc
@@ -184,12 +184,19 @@
   DCHECK(base::SequencedTaskRunnerHandle::IsSet());
   DCHECK_EQ(State::INITIALIZING, initialization_state_);
 
+  // Log the warning/error messages only in the first initialization to prevent
+  // spamming during the retries of initialization.
+  static bool first_initialization_ = true;
+
   // Check for accelerometer symlink which will be created by the udev rules
   // file on detecting the device.
   if (base::IsDirectoryEmpty(base::FilePath(kAccelerometerDevicePath))) {
     if (base::SysInfo::IsRunningOnChromeOS()) {
-      LOG(WARNING) << "Accelerometer device directory is empty at "
-                   << kAccelerometerDevicePath;
+      if (first_initialization_) {
+        LOG(WARNING) << "Accelerometer device directory is empty at "
+                     << kAccelerometerDevicePath;
+      }
+      first_initialization_ = false;
       TryScheduleInitializeInternal();
     } else {
       initialization_state_ = State::FAILED;
@@ -236,7 +243,9 @@
   }
   if (configuration_.trigger_now.empty()) {
     if (base::SysInfo::IsRunningOnChromeOS()) {
-      LOG(ERROR) << "Accelerometer trigger not found";
+      if (first_initialization_)
+        LOG(ERROR) << "Accelerometer trigger not found";
+      first_initialization_ = false;
       TryScheduleInitializeInternal();
     } else {
       initialization_state_ = State::FAILED;
diff --git a/ash/system/phonehub/notification_opt_in_view.cc b/ash/system/phonehub/notification_opt_in_view.cc
index 192294e..80e50a2 100644
--- a/ash/system/phonehub/notification_opt_in_view.cc
+++ b/ash/system/phonehub/notification_opt_in_view.cc
@@ -156,8 +156,13 @@
 
 void NotificationOptInView::UpdateVisibility() {
   DCHECK(notification_access_manager_);
+
+  // Can only request access if it is available but has not yet been granted.
+  bool can_request_access = notification_access_manager_->GetAccessStatus() ==
+                            chromeos::phonehub::NotificationAccessManager::
+                                AccessStatus::kAvailableButNotGranted;
   const bool should_show =
-      !notification_access_manager_->HasAccessBeenGranted() &&
+      can_request_access &&
       !notification_access_manager_->HasNotificationSetupUiBeenDismissed();
   SetVisible(should_show);
 }
diff --git a/ash/system/phonehub/phone_hub_tray_unittest.cc b/ash/system/phonehub/phone_hub_tray_unittest.cc
index b29292cc..0ffa6dae 100644
--- a/ash/system/phonehub/phone_hub_tray_unittest.cc
+++ b/ash/system/phonehub/phone_hub_tray_unittest.cc
@@ -205,7 +205,9 @@
 }
 
 TEST_F(PhoneHubTrayTest, ShowNotificationOptInViewWhenAccessNotGranted) {
-  GetNotificationAccessManager()->SetHasAccessBeenGrantedInternal(false);
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::
+          kAvailableButNotGranted);
 
   ClickTrayButton();
 
@@ -223,7 +225,19 @@
 }
 
 TEST_F(PhoneHubTrayTest, HideNotificationOptInViewWhenAccessHasBeenGranted) {
-  GetNotificationAccessManager()->SetHasAccessBeenGrantedInternal(true);
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::
+          kAccessGranted);
+
+  ClickTrayButton();
+
+  EXPECT_TRUE(notification_opt_in_view());
+  EXPECT_FALSE(notification_opt_in_view()->GetVisible());
+}
+
+TEST_F(PhoneHubTrayTest, HideNotificationOptInViewWhenAccessIsProhibited) {
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::kProhibited);
 
   ClickTrayButton();
 
@@ -232,7 +246,9 @@
 }
 
 TEST_F(PhoneHubTrayTest, StartNotificationSetUpFlow) {
-  GetNotificationAccessManager()->SetHasAccessBeenGrantedInternal(false);
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::
+          kAvailableButNotGranted);
 
   ClickTrayButton();
   EXPECT_TRUE(notification_opt_in_view());
@@ -251,13 +267,17 @@
   ClickOnAndWait(notification_opt_in_set_up_button());
 
   // Simulate that notification access has been granted.
-  GetNotificationAccessManager()->SetHasAccessBeenGrantedInternal(true);
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::
+          kAccessGranted);
 
   // This view should be dismissed.
   EXPECT_FALSE(notification_opt_in_view()->GetVisible());
 
   // Simulate that notification access has been revoked by the phone.
-  GetNotificationAccessManager()->SetHasAccessBeenGrantedInternal(false);
+  GetNotificationAccessManager()->SetAccessStatusInternal(
+      chromeos::phonehub::NotificationAccessManager::AccessStatus::
+          kAvailableButNotGranted);
 
   // This view should show up again.
   EXPECT_TRUE(notification_opt_in_view()->GetVisible());
diff --git a/ash/wm/desks/autotest_desks_api.cc b/ash/wm/desks/autotest_desks_api.cc
index ef0cc64..091f195 100644
--- a/ash/wm/desks/autotest_desks_api.cc
+++ b/ash/wm/desks/autotest_desks_api.cc
@@ -112,7 +112,7 @@
 
     auto* animation = DesksController::Get()->animation();
     DCHECK(animation);
-    animation_layer_ = animation->GetDeskSwitchAnimatorAtIndexForTesting(0)
+    animation_layer_ = animation->GetFirstDeskSwitchAnimatorForTesting()
                            ->GetAnimationLayerForTesting();
     animation_layer_->GetAnimator()->AddObserver(this);
   }
diff --git a/ash/wm/desks/desk_animation_base.cc b/ash/wm/desks/desk_animation_base.cc
index 479e4d2..f99eb82 100644
--- a/ash/wm/desks/desk_animation_base.cc
+++ b/ash/wm/desks/desk_animation_base.cc
@@ -143,9 +143,9 @@
 }
 
 RootWindowDeskSwitchAnimator*
-DeskAnimationBase::GetDeskSwitchAnimatorAtIndexForTesting(size_t index) const {
-  DCHECK_LT(index, desk_switch_animators_.size());
-  return desk_switch_animators_[index].get();
+DeskAnimationBase::GetFirstDeskSwitchAnimatorForTesting() const {
+  DCHECK(!desk_switch_animators_.empty());
+  return desk_switch_animators_.front().get();
 }
 
 }  // namespace ash
diff --git a/ash/wm/desks/desk_animation_base.h b/ash/wm/desks/desk_animation_base.h
index 2017549..684d193 100644
--- a/ash/wm/desks/desk_animation_base.h
+++ b/ash/wm/desks/desk_animation_base.h
@@ -62,8 +62,7 @@
     skip_notify_controller_on_animation_finished_for_testing_ = val;
   }
 
-  RootWindowDeskSwitchAnimator* GetDeskSwitchAnimatorAtIndexForTesting(
-      size_t index) const;
+  RootWindowDeskSwitchAnimator* GetFirstDeskSwitchAnimatorForTesting() const;
 
  protected:
   // Abstract functions that can be overridden by child classes to do different
diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc
index ca00c9e..75d82655a 100644
--- a/ash/wm/desks/desk_animation_impl.cc
+++ b/ash/wm/desks/desk_animation_impl.cc
@@ -129,8 +129,7 @@
   }
 
   // Activate the target desk and take a screenshot.
-  // TODO(crbug.com/1134390): Convert back to DCHECK when the issue is fixed.
-  CHECK_EQ(pending_animators.size(), desk_switch_animators_.size());
+  DCHECK_EQ(pending_animators.size(), desk_switch_animators_.size());
   PrepareDeskForScreenshot(new_ending_desk_index);
   for (auto* animator : pending_animators)
     animator->TakeEndingDeskScreenshot();
@@ -143,27 +142,28 @@
 
   presentation_time_recorder_->RequestNext();
 
-  // If any of the displays need a new screenshot while scrolling, take the
-  // ending desk screenshot for all of them to keep them in sync.
-  base::Optional<int> ending_desk_index;
+  // List of animators that need a screenshot. It should be either empty or
+  // match the size of |desk_switch_animators_| as all the animations should be
+  // in sync.
+  std::vector<RootWindowDeskSwitchAnimator*> pending_animators;
   for (const auto& animator : desk_switch_animators_) {
-    if (!ending_desk_index)
-      ending_desk_index = animator->UpdateSwipeAnimation(scroll_delta_x);
-    else
-      animator->UpdateSwipeAnimation(scroll_delta_x);
+    if (animator->UpdateSwipeAnimation(scroll_delta_x))
+      pending_animators.push_back(animator.get());
   }
 
   // No screenshot needed.
-  if (!ending_desk_index)
+  if (pending_animators.empty()) {
+    OnEndingDeskScreenshotTaken();
     return true;
+  }
 
   // Activate the target desk and take a screenshot.
-  ending_desk_index_ = *ending_desk_index;
+  // TODO(crbug.com/1134390): Convert back to DCHECK when the issue is fixed.
+  CHECK_EQ(pending_animators.size(), desk_switch_animators_.size());
+  ending_desk_index_ = desk_switch_animators_[0]->ending_desk_index();
   PrepareDeskForScreenshot(ending_desk_index_);
-  for (const auto& animator : desk_switch_animators_) {
-    animator->PrepareForEndingDeskScreenshot(ending_desk_index_);
+  for (auto* animator : pending_animators)
     animator->TakeEndingDeskScreenshot();
-  }
   return true;
 }
 
diff --git a/ash/wm/desks/desk_animation_impl_unittest.cc b/ash/wm/desks/desk_animation_impl_unittest.cc
index b9267a3..e8f9206 100644
--- a/ash/wm/desks/desk_animation_impl_unittest.cc
+++ b/ash/wm/desks/desk_animation_impl_unittest.cc
@@ -8,8 +8,6 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
-#include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
-#include "base/barrier_closure.h"
 #include "base/test/scoped_feature_list.h"
 
 namespace ash {
@@ -35,45 +33,4 @@
   animation.EndSwipeAnimation();
 }
 
-// Tests that there is no crash when swiping with external displays. Regression
-// test for https://crbug.com/1154868.
-TEST_F(DeskActivationAnimationTest, UpdateSwipeNewScreenshotCrash) {
-  // Crash is only reproducible on different resolution widths and easier to
-  // repro when the widths differ by a lot.
-  UpdateDisplay("600x600,601+0-2000x600");
-
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kEnhancedDeskAnimations);
-
-  // Crash repro requires three desks.
-  auto* desks_controller = DesksController::Get();
-  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
-  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
-
-  DeskActivationAnimation animation(desks_controller, 1, 2,
-                                    DesksSwitchSource::kDeskSwitchTouchpad,
-                                    /*update_window_activation=*/false);
-  animation.set_skip_notify_controller_on_animation_finished_for_testing(true);
-  animation.Launch();
-
-  // Wait until all ending screenshots have been taken before swiping.
-  size_t num_animators = 2u;
-  base::RunLoop run_loop;
-  base::RepeatingClosure end_screenshot_callback =
-      base::BarrierClosure(num_animators, run_loop.QuitClosure());
-  for (size_t i = 0; i < num_animators; ++i) {
-    auto* desk_switch_animator =
-        animation.GetDeskSwitchAnimatorAtIndexForTesting(i);
-    RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator)
-        .SetOnEndingScreenshotTakenCallback(end_screenshot_callback);
-  }
-  run_loop.Run();
-
-  // Swipe in a way which would have caused a crash using the old algorithm. See
-  // bug for more details.
-  animation.UpdateSwipeAnimation(-20);
-  animation.UpdateSwipeAnimation(10);
-  animation.EndSwipeAnimation();
-}
-
 }  // namespace ash
diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc
index fbb932e35..79f9db8b 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator.cc
@@ -248,10 +248,9 @@
   return true;
 }
 
-base::Optional<int> RootWindowDeskSwitchAnimator::UpdateSwipeAnimation(
-    float scroll_delta_x) {
+bool RootWindowDeskSwitchAnimator::UpdateSwipeAnimation(float scroll_delta_x) {
   if (!starting_desk_screenshot_taken_ || !ending_desk_screenshot_taken_)
-    return base::nullopt;
+    return false;
 
   const float translation_delta_x =
       TouchpadToXTranslation(scroll_delta_x, x_translation_offset_);
@@ -305,16 +304,13 @@
           : transformed_animation_layer_bounds.x() >
                 -kMinDistanceBeforeScreenshotDp;
 
-  // TODO(sammiequon): Make GetIndexOfMostVisibleDeskScreenshot() public and
-  // have DeskActivationAnimation keep track of |visible_desk_index_|. Right now
-  // OnVisibleDeskChanged will get called once for each display.
   const int old_visible_desk_index = visible_desk_index_;
   visible_desk_index_ = GetIndexOfMostVisibleDeskScreenshot();
   if (old_visible_desk_index != visible_desk_index_)
     delegate_->OnVisibleDeskChanged();
 
   if (!going_out_of_bounds)
-    return base::nullopt;
+    return false;
 
   // The upcoming desk we need to show will be an adjacent desk to the desk at
   // |visible_desk_index_| based on |moving_left|.
@@ -322,17 +318,13 @@
 
   if (new_desk_index < 0 ||
       new_desk_index >= int{DesksController::Get()->desks().size()}) {
-    return base::nullopt;
+    return false;
   }
 
-  return new_desk_index;
-}
-
-void RootWindowDeskSwitchAnimator::PrepareForEndingDeskScreenshot(
-    int new_ending_desk_index) {
-  ending_desk_index_ = new_ending_desk_index;
+  ending_desk_index_ = new_desk_index;
   ending_desk_screenshot_retries_ = 0;
   ending_desk_screenshot_taken_ = false;
+  return true;
 }
 
 int RootWindowDeskSwitchAnimator::EndSwipeAnimation() {
diff --git a/ash/wm/desks/root_window_desk_switch_animator.h b/ash/wm/desks/root_window_desk_switch_animator.h
index 50dd8d3..9bb431ba 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.h
+++ b/ash/wm/desks/root_window_desk_switch_animator.h
@@ -263,17 +263,8 @@
   // Called as a user is performing a touchpad swipe. Requests a new screenshot
   // if necessary based on the last direction as specified in |scroll_delta_x|.
   // |scroll_delta_x| is in touchpad units, it will be converted to display
-  // units and then used to shift the animation layer. If the animation layer is
-  // near its boundaries, this will return an index for the desk we should take
-  // a screenshot for. If we are not near the boundaries, or if there is no next
-  // adjacent desk in the direction we are heading, return base::nullopt. The
-  // delegate is responsible for requesting the screenshot.
-  base::Optional<int> UpdateSwipeAnimation(float scroll_delta_x);
-
-  // Maybe called after UpdateSwipeAnimation() if we need a new screenshot.
-  // Updates |ending_desk_index_| and resets some other internal state related
-  // to the ending desk screenshot.
-  void PrepareForEndingDeskScreenshot(int new_ending_desk_index);
+  // units and then used to shift the animation layer.
+  bool UpdateSwipeAnimation(float scroll_delta_x);
 
   // Called when a user ends a touchpad swipe. This will animate to the most
   // visible desk, whose index is also returned.
diff --git a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
index 921b8bf..7ad2418 100644
--- a/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator_unittest.cc
@@ -486,18 +486,12 @@
   // Swipe enough so that our third and fourth desk screenshots are taken, and
   // then swipe so that the fourth desk is fully shown. There should be 3
   // visible desk changes in total.
-  base::Optional<int> new_index =
-      animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change);
-  ASSERT_TRUE(new_index.has_value());
-  animator()->PrepareForEndingDeskScreenshot(*new_index);
+  ASSERT_TRUE(
+      animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change));
   TakeEndingDeskScreenshotAndWait();
-
-  new_index =
-      animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change);
-  ASSERT_TRUE(new_index.has_value());
-  animator()->PrepareForEndingDeskScreenshot(*new_index);
+  ASSERT_TRUE(
+      animator()->UpdateSwipeAnimation(-touchpad_swipe_length_for_desk_change));
   TakeEndingDeskScreenshotAndWait();
-
   animator()->UpdateSwipeAnimation(-3 * touchpad_swipe_length_for_desk_change);
   EXPECT_EQ(3, visible_desk_changed_count());
 
diff --git a/ash/wm/gestures/wm_gesture_handler_unittest.cc b/ash/wm/gestures/wm_gesture_handler_unittest.cc
index 3141538e..4bf8c91 100644
--- a/ash/wm/gestures/wm_gesture_handler_unittest.cc
+++ b/ash/wm/gestures/wm_gesture_handler_unittest.cc
@@ -129,7 +129,7 @@
       auto* animation = DesksController::Get()->animation();
       DCHECK(animation);
       auto* desk_switch_animator =
-          animation->GetDeskSwitchAnimatorAtIndexForTesting(0);
+          animation->GetFirstDeskSwitchAnimatorForTesting();
       base::RunLoop run_loop;
       RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator)
           .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure());
diff --git a/base/allocator/partition_allocator/page_allocator.h b/base/allocator/partition_allocator/page_allocator.h
index f76bbfa..d964f2be 100644
--- a/base/allocator/partition_allocator/page_allocator.h
+++ b/base/allocator/partition_allocator/page_allocator.h
@@ -31,7 +31,8 @@
   // Enforces permission update (Decommit will set to PageInaccessible;
   // Recommit will set to whatever was requested, other than PageInaccessible).
   PageUpdatePermissions,
-  // Will not update permissions, if the platform supports that (POSIX only).
+  // Will not update permissions, if the platform supports that (POSIX & Fuchsia
+  // only).
   PageKeepPermissionsIfPossible,
 };
 
@@ -105,8 +106,8 @@
 //
 // |accessibility_disposition| allows to specify whether the pages should be
 // made inaccessible (PageUpdatePermissions), or left as is
-// (PageKeepPermissionsIfPossible, POSIX only). The latter should only be used
-// as an optimization if you really know what you're doing.
+// (PageKeepPermissionsIfPossible, POSIX & Fuchsia only). The latter should only
+// be used as an optimization if you really know what you're doing.
 // TODO(bartekn): Ideally, all callers should use PageUpdatePermissions,
 // for better security, but that may lead to a perf regression. Tracked at
 // http://crbug.com/766882.
@@ -122,7 +123,7 @@
 //
 // Note: "Committed memory" is a Windows Memory Subsystem concept that ensures
 // processes will not fault when touching a committed memory region. There is
-// no analogue in the POSIX memory API where virtual memory pages are
+// no analogue in the POSIX & Fuchsia memory API where virtual memory pages are
 // best-effort allocated resources on the first touch. If PageUpdatePermissions
 // disposition is used, this API behaves in a platform-agnostic way by
 // simulating the Windows "decommit" state by both discarding the region
@@ -142,8 +143,8 @@
 //
 // |accessibility_disposition| allows to specify whether the page permissions
 // should be set to |page_accessibility| (PageUpdatePermissions), or left as is
-// (PageKeepPermissionsIfPossible, POSIX only). The latter can only be used if
-// the pages were previously accessible and decommitted with
+// (PageKeepPermissionsIfPossible, POSIX & Fuchsia only). The latter can only be
+// used if the pages were previously accessible and decommitted with
 // PageKeepPermissionsIfPossible. It is ok, however, to recommit with
 // PageUpdatePermissions even if pages were decommitted with
 // PageKeepPermissionsIfPossible (merely losing an optimization).
diff --git a/base/allocator/partition_allocator/page_allocator_internals_fuchsia.h b/base/allocator/partition_allocator/page_allocator_internals_fuchsia.h
index c4c4e79..1cc8be2 100644
--- a/base/allocator/partition_allocator/page_allocator_internals_fuchsia.h
+++ b/base/allocator/partition_allocator/page_allocator_internals_fuchsia.h
@@ -184,10 +184,9 @@
     void* address,
     size_t length,
     PageAccessibilityDisposition accessibility_disposition) {
-  // TODO(bartekn): Ignoring accessibility_disposition preserves the behavior
-  // the API had since its conception, but consider similar optimization to
-  // POSIX.
-  SetSystemPagesAccessInternal(address, length, PageInaccessible);
+  if (accessibility_disposition == PageUpdatePermissions) {
+    SetSystemPagesAccess(address, length, PageInaccessible);
+  }
 
   // TODO(https://crbug.com/1022062): Review whether this implementation is
   // still appropriate once DiscardSystemPagesInternal() migrates to a "lazy"
@@ -200,10 +199,12 @@
     size_t length,
     PageAccessibilityConfiguration accessibility,
     PageAccessibilityDisposition accessibility_disposition) {
-  // TODO(bartekn): Ignoring accessibility_disposition preserves the behavior
-  // the API had since its conception, but consider similar optimization to
-  // POSIX.
-  SetSystemPagesAccessInternal(address, length, accessibility);
+  // On Fuchsia systems, the caller needs to simply read the memory to recommit
+  // it. However, if decommit changed the permissions, recommit has to change
+  // them back.
+  if (accessibility_disposition == PageUpdatePermissions) {
+    SetSystemPagesAccess(address, length, accessibility);
+  }
 }
 
 }  // namespace base
diff --git a/base/allocator/partition_allocator/page_allocator_internals_posix.h b/base/allocator/partition_allocator/page_allocator_internals_posix.h
index 503fa7dd..f3c1c53b 100644
--- a/base/allocator/partition_allocator/page_allocator_internals_posix.h
+++ b/base/allocator/partition_allocator/page_allocator_internals_posix.h
@@ -268,11 +268,9 @@
     size_t length,
     PageAccessibilityConfiguration accessibility,
     PageAccessibilityDisposition accessibility_disposition) {
-  // On POSIX systems, the caller need simply read the memory to recommit it.
-  // This has the correct behavior because the API requires the permissions to
-  // be the same as before decommitting and all configurations can read.
-  // However, if decommit changed the permissions, recommit has to change them
-  // back.
+  // On POSIX systems, the caller needs to simply read the memory to recommit
+  // it. However, if decommit changed the permissions, recommit has to change
+  // them back.
   if (accessibility_disposition == PageUpdatePermissions) {
     SetSystemPagesAccess(address, length, accessibility);
   }
diff --git a/base/android/java/src/org/chromium/base/process_launcher/BindService.java b/base/android/java/src/org/chromium/base/process_launcher/BindService.java
index c2c3dff..0a480140 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/BindService.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/BindService.java
@@ -16,7 +16,6 @@
 import android.os.UserHandle;
 
 import org.chromium.base.BuildConfig;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.compat.ApiHelperForQ;
 
 import java.lang.reflect.Method;
@@ -29,7 +28,8 @@
     private static Method sBindServiceAsUserMethod;
 
     static boolean supportVariableConnections() {
-        return BuildInfo.isAtLeastQ() && !BuildConfig.IS_INCREMENTAL_INSTALL;
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+                && !BuildConfig.IS_INCREMENTAL_INSTALL;
     }
 
     // Note that handler is not guaranteed to be used, and client still need to correctly handle
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 55604da..7604a4c 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20201206.1.1
+0.20201206.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 55604da..7604a4c 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20201206.1.1
+0.20201206.3.1
diff --git a/cc/input/threaded_input_handler.cc b/cc/input/threaded_input_handler.cc
index 97afaf5..1aeb84a 100644
--- a/cc/input/threaded_input_handler.cc
+++ b/cc/input/threaded_input_handler.cc
@@ -951,11 +951,14 @@
     commit_data->manipulation_info |= kManipulationInfoPrecisionTouchPad;
   if (has_pinch_zoomed_)
     commit_data->manipulation_info |= kManipulationInfoPinchZoom;
+  if (has_scrolled_by_scrollbar_)
+    commit_data->manipulation_info |= kManipulationInfoScrollbar;
 
   has_scrolled_by_wheel_ = false;
   has_scrolled_by_touch_ = false;
   has_scrolled_by_precisiontouchpad_ = false;
   has_pinch_zoomed_ = false;
+  has_scrolled_by_scrollbar_ = false;
 
   commit_data->scroll_gesture_did_end = scroll_gesture_did_end_;
   scroll_gesture_did_end_ = false;
@@ -2093,6 +2096,8 @@
     has_scrolled_by_wheel_ = true;
   } else if (type == ui::ScrollInputType::kTouchscreen) {
     has_scrolled_by_touch_ = true;
+  } else if (type == ui::ScrollInputType::kScrollbar) {
+    has_scrolled_by_scrollbar_ = true;
   }
 }
 
diff --git a/cc/input/threaded_input_handler.h b/cc/input/threaded_input_handler.h
index d84d2eb..37e4068 100644
--- a/cc/input/threaded_input_handler.h
+++ b/cc/input/threaded_input_handler.h
@@ -434,6 +434,7 @@
   bool has_scrolled_by_wheel_ = false;
   bool has_scrolled_by_touch_ = false;
   bool has_scrolled_by_precisiontouchpad_ = false;
+  bool has_scrolled_by_scrollbar_ = false;
 
   // Must be the last member to ensure this is destroyed first in the
   // destruction order and invalidates all weak pointers.
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 1e22e6d..ab6116e4 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -435,14 +435,8 @@
       base::TimeDelta first_scroll_delay,
       base::TimeTicks first_scroll_timestamp) override {}
 
-  void RecordManipulationTypeCounts(ManipulationInfo info) override {}
-
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      ElementId scroll_latched_element_id) override {}
-
-  void SendScrollEndEventFromImplSide(
-      ElementId scroll_latched_element_id) override {}
+  void UpdateCompositorScrollState(
+      const CompositorCommitData& commit_data) override {}
 
   void RequestNewLayerTreeFrameSink() override {
     test_hooks_->RequestNewLayerTreeFrameSink();
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index 3964f971..1fb6938 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -34,12 +34,8 @@
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override {}
   void ApplyViewportChanges(const ApplyViewportChangesArgs&) override {}
-  void RecordManipulationTypeCounts(ManipulationInfo info) override {}
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      ElementId scroll_latched_element_id) override {}
-  void SendScrollEndEventFromImplSide(
-      ElementId scroll_latched_element_id) override {}
+  void UpdateCompositorScrollState(
+      const CompositorCommitData& commit_data) override {}
   void RequestNewLayerTreeFrameSink() override {}
   void DidInitializeLayerTreeFrameSink() override {}
   void DidFailToInitializeLayerTreeFrameSink() override {}
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 8762cb6..3243ab7 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -918,29 +918,6 @@
   SetNeedsUpdateLayers();
 }
 
-void LayerTreeHost::RecordManipulationTypeCounts(
-    const CompositorCommitData& commit_data) {
-  client_->RecordManipulationTypeCounts(commit_data.manipulation_info);
-}
-
-void LayerTreeHost::SendOverscrollAndScrollEndEventsFromImplSide(
-    const CompositorCommitData& commit_data) {
-  if (commit_data.scroll_latched_element_id == ElementId())
-    return;
-
-  if (!commit_data.overscroll_delta.IsZero()) {
-    client_->SendOverscrollEventFromImplSide(
-        commit_data.overscroll_delta, commit_data.scroll_latched_element_id);
-  }
-  // TODO(bokan): If a scroll ended and a new one began in the same Blink frame
-  // (e.g. during a long running main thread task), this will erroneously
-  // dispatch the scroll end to the latter (still-scrolling) element.
-  // https://crbug.com/1116780.
-  if (commit_data.scroll_gesture_did_end)
-    client_->SendScrollEndEventFromImplSide(
-        commit_data.scroll_latched_element_id);
-}
-
 void LayerTreeHost::UpdateScrollOffsetFromImpl(
     const ElementId& id,
     const gfx::ScrollOffset& delta,
@@ -1016,14 +993,12 @@
     }
   }
 
-  SendOverscrollAndScrollEndEventsFromImplSide(*commit_data);
+  client_->UpdateCompositorScrollState(*commit_data);
 
   // This needs to happen after scroll deltas have been sent to prevent top
   // controls from clamping the layout viewport both on the compositor and
   // on the main thread.
   ApplyViewportChanges(*commit_data);
-
-  RecordManipulationTypeCounts(*commit_data);
 }
 
 void LayerTreeHost::ApplyMutatorEvents(std::unique_ptr<MutatorEvents> events) {
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 28571f2..b4179ad 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -778,9 +778,6 @@
   enum { kNumFramesToConsiderBeforeRemovingSlowPathFlag = 60 };
 
   void ApplyViewportChanges(const CompositorCommitData& commit_data);
-  void RecordManipulationTypeCounts(const CompositorCommitData& commit_data);
-  void SendOverscrollAndScrollEndEventsFromImplSide(
-      const CompositorCommitData& commit_data);
   void ApplyPageScaleDeltaFromImplSide(float page_scale_delta);
   void InitializeProxy(std::unique_ptr<Proxy> proxy);
 
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index 562f9d0..4a8332b 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -11,6 +11,7 @@
 #include "base/time/time.h"
 #include "cc/input/browser_controls_state.h"
 #include "cc/metrics/frame_sequence_tracker_collection.h"
+#include "cc/trees/property_tree.h"
 #include "ui/gfx/geometry/scroll_offset.h"
 #include "ui/gfx/geometry/vector2d_f.h"
 
@@ -24,7 +25,6 @@
 
 namespace cc {
 struct BeginMainFrameMetrics;
-struct ElementId;
 struct WebVitalMetrics;
 
 struct ApplyViewportChangesArgs {
@@ -66,6 +66,7 @@
 constexpr ManipulationInfo kManipulationInfoTouch = 1 << 1;
 constexpr ManipulationInfo kManipulationInfoPrecisionTouchPad = 1 << 2;
 constexpr ManipulationInfo kManipulationInfoPinchZoom = 1 << 3;
+constexpr ManipulationInfo kManipulationInfoScrollbar = 1 << 4;
 
 struct PaintBenchmarkResult {
   double record_time_ms = 0;
@@ -137,17 +138,10 @@
   // related to pinch-zoom, browser controls (aka URL bar), overscroll, etc.
   virtual void ApplyViewportChanges(const ApplyViewportChangesArgs& args) = 0;
 
-  // Record use counts of different methods of scrolling (e.g. wheel, touch,
-  // precision touchpad, etc.).
-  virtual void RecordManipulationTypeCounts(ManipulationInfo info) = 0;
-
-  // Notifies the client when an overscroll has happened.
-  virtual void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      ElementId scroll_latched_element_id) = 0;
-  // Notifies the client when a gesture scroll has ended.
-  virtual void SendScrollEndEventFromImplSide(
-      ElementId scroll_latched_element_id) = 0;
+  // Notifies the client about scroll and input related changes that occurred in
+  // the LayerTreeHost since the last commit.
+  virtual void UpdateCompositorScrollState(
+      const CompositorCommitData& commit_data) = 0;
 
   // Request a LayerTreeFrameSink from the client. When the client has one it
   // should call LayerTreeHost::SetLayerTreeFrameSink. This will result in
diff --git a/chrome/VERSION b/chrome/VERSION
index ac373c28..d191b27c 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=89
 MINOR=0
-BUILD=4348
+BUILD=4349
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 83a6780..598ddef 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1249,7 +1249,6 @@
   "java/src/org/chromium/chrome/browser/signin/ConfirmSyncDataStateMachine.java",
   "java/src/org/chromium/chrome/browser/signin/ConfirmSyncDataStateMachineDelegate.java",
   "java/src/org/chromium/chrome/browser/signin/GoogleActivityController.java",
-  "java/src/org/chromium/chrome/browser/signin/IdentityServicesProvider.java",
   "java/src/org/chromium/chrome/browser/signin/PersonalizedSigninPromoView.java",
   "java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java",
   "java/src/org/chromium/chrome/browser/signin/SignOutDialogFragment.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/IdentityServicesProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/IdentityServicesProvider.java
deleted file mode 100644
index 7b3157c..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/IdentityServicesProvider.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.signin;
-
-/**
- * Provides access to sign-in related services that are profile-keyed on the native side. Java
- * equivalent of AccountTrackerServiceFactory and similar classes.
- *
- * TODO(https://crbug.com/1152718): Remove this class after cleaning up downstream dependencies in
- * //clank
- */
-@Deprecated
-public class IdentityServicesProvider
-        extends org.chromium.chrome.browser.signin.services.IdentityServicesProvider {}
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index cab3dcb9..891cca2 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -18,13 +18,13 @@
   <message name="IDS_SETTINGS_SECONDARY_USER_BANNER" desc="Banner displayed in settings page when the user is secondary in a multi-profile session.">
     Some settings belonging to <ph name="PRIMARY_EMAIL">$1<ex>john@google.com</ex></ph> are being shared with you. These settings only affect your account when using multiple sign-in.
   </message>
-  <message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS" desc="Banner displayed in OS settings page in case update is required by policy but device has reached end-of-life and the days remaining to return the device back to the enterprise is less than seven.">
+  <message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS" desc="Banner displayed in OS settings page in case update is required by policy, but the device has reached end-of-life and the number of days remaining to return the device back to the manager is less than seven. MANAGER can be a domain or an email address.">
     {NUM_DAYS, plural,
-      =1 {<ph name="DOMAIN">{1}<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">{2}<ex>Chromebook</ex></ph> today.<ph name="LINK_BEGIN">&lt;a target="_blank" href="{3}<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>}
-      other {<ph name="DOMAIN">{1}<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">{2}<ex>Chromebook</ex></ph> within {NUM_DAYS} days.<ph name="LINK_BEGIN">&lt;a target="_blank" href="{3}<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>}}
+      =1 {<ph name="MANAGER">{1}<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">{2}<ex>Chromebook</ex></ph> today.<ph name="LINK_BEGIN">&lt;a target="_blank" href="{3}<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>}
+      other {<ph name="MANAGER">{1}<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">{2}<ex>Chromebook</ex></ph> within {NUM_DAYS} days.<ph name="LINK_BEGIN">&lt;a target="_blank" href="{3}<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>}}
   </message>
-  <message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK" desc="Banner displayed in OS settings page in case update is required by policy but device has reached end-of-life and the days remaining to return the device back to the enterprise is equal to seven.">
-    <ph name="DOMAIN">$1<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">$2<ex>Chromebook</ex></ph> within 1 week.<ph name="LINK_BEGIN">&lt;a target="_blank" href="$3<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>
+  <message name="IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK" desc="Banner displayed in OS settings page in case update is required by policy, but the device has reached end-of-life and the number of days remaining to return the device back to the manager is equal to seven. MANAGER can be a domain or an email address.">
+    <ph name="MANAGER">$1<ex>example.com</ex></ph> requires you to back up your data and return this <ph name="DEVICE_TYPE">$2<ex>Chromebook</ex></ph> within 1 week.<ph name="LINK_BEGIN">&lt;a target="_blank" href="$3<ex>https://google.com/</ex>"&gt;</ph>See details<ph name="LINK_END">&lt;/a&gt;</ph>
   </message>
 
   <!-- Settings Search Box -->
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS.png.sha1
index 68897631..c94f944f 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS.png.sha1
@@ -1 +1 @@
-7fb20039ee560a2011c23caa90be95ad49cb8a2a
\ No newline at end of file
+ac20dd522d48d3b3ec5595bb7ec9875856d36d7b
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK.png.sha1
index 94e41e2..c94f944f 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_ONE_WEEK.png.sha1
@@ -1 +1 @@
-9e08260e0f4b0af62a9b344a5584615126caac3d
\ No newline at end of file
+ac20dd522d48d3b3ec5595bb7ec9875856d36d7b
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 4ddee9c..81b9dfe 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1253,9 +1253,6 @@
   <message name="IDS_SETTINGS_SAFETY_CHECK_WEAK_PASSWORDS" desc="This shows the amount of weak passwords that the Chrome password check found.">
     {NUM_WEAK, plural, =0 {} =1 {1 weak password} other {{NUM_WEAK} weak passwords}}
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_STRING_TUPLE_WITH_COMMA" desc="This string lists two elements, via concatenating them with a comma in the English language.">
-    <ph name="TYPE_1">$1<ex>2 compromised passwords</ex></ph>, <ph name="TYPE_2">$2<ex>7 weak passwords</ex></ph>
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_BUTTON_ARIA_LABEL" desc="Accessibility text for the button that allows users to review their passwords.">
     Review passwords
   </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0cde3498..7c86106 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3314,6 +3314,10 @@
      flag_descriptions::kDesktopPWAsAppIconShortcutsMenuName,
      flag_descriptions::kDesktopPWAsAppIconShortcutsMenuDescription, kOsWin,
      FEATURE_VALUE_TYPE(features::kDesktopPWAsAppIconShortcutsMenu)},
+    {"enable-desktop-pwas-elided-extensions-menu",
+     flag_descriptions::kDesktopPWAsElidedExtensionsMenuName,
+     flag_descriptions::kDesktopPWAsElidedExtensionsMenuDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kDesktopPWAsElidedExtensionsMenu)},
     {"enable-desktop-pwas-tab-strip",
      flag_descriptions::kDesktopPWAsTabStripName,
      flag_descriptions::kDesktopPWAsTabStripDescription, kOsDesktop,
@@ -3714,6 +3718,10 @@
      flag_descriptions::kImeServiceDecoderName,
      flag_descriptions::kImeServiceDecoderDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kImeMojoDecoder)},
+    {"enable-cros-ime-system-emoji-picker",
+     flag_descriptions::kImeSystemEmojiPickerName,
+     flag_descriptions::kImeSystemEmojiPickerDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kImeSystemEmojiPicker)},
     {"enable-cros-language-settings-update",
      flag_descriptions::kCrosLanguageSettingsUpdateName,
      flag_descriptions::kCrosLanguageSettingsUpdateDescription, kOsCrOS,
diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc
index ac2be7b..64a36ce 100644
--- a/chrome/browser/android/compositor/tab_content_manager.cc
+++ b/chrome/browser/android/compositor/tab_content_manager.cc
@@ -343,10 +343,10 @@
     const base::android::JavaParamRef<jobject>& j_callback) {
   thumbnail_cache_->DecompressThumbnailFromFile(
       tab_id,
-      base::BindRepeating(
-          &TabContentManager::SendThumbnailToJava, weak_factory_.GetWeakPtr(),
-          base::android::ScopedJavaGlobalRef<jobject>(j_callback),
-          /* need_downsampling */ true));
+      base::BindOnce(&TabContentManager::SendThumbnailToJava,
+                     weak_factory_.GetWeakPtr(),
+                     base::android::ScopedJavaGlobalRef<jobject>(j_callback),
+                     /* need_downsampling */ true));
 }
 
 void TabContentManager::OnUIResourcesWereEvicted() {
diff --git a/chrome/browser/apps/app_service/web_apps_base.cc b/chrome/browser/apps/app_service/web_apps_base.cc
index 54cf528..62ee93b 100644
--- a/chrome/browser/apps/app_service/web_apps_base.cc
+++ b/chrome/browser/apps/app_service/web_apps_base.cc
@@ -81,7 +81,7 @@
   std::vector<apps::mojom::ConditionValuePtr> mime_type_condition_values;
   for (auto& mime_type : content_types) {
     mime_type_condition_values.push_back(apps_util::MakeConditionValue(
-        mime_type, apps::mojom::PatternMatchType::kNone));
+        mime_type, apps::mojom::PatternMatchType::kMimeType));
   }
   if (!mime_type_condition_values.empty()) {
     auto mime_type_condition =
diff --git a/chrome/browser/apps/app_service/web_apps_base_browsertest.cc b/chrome/browser/apps/app_service/web_apps_base_browsertest.cc
index d197812..3f83438 100644
--- a/chrome/browser/apps/app_service/web_apps_base_browsertest.cc
+++ b/chrome/browser/apps/app_service/web_apps_base_browsertest.cc
@@ -92,8 +92,9 @@
 }
 
 void CheckShareFileFilter(const apps::mojom::IntentFilterPtr& intent_filter,
-                          const std::vector<std::string>& content_types,
-                          const std::string& different_content_type) {
+                          const std::vector<std::string>& filter_types,
+                          const std::vector<std::string>& accepted_types,
+                          const std::vector<std::string>& rejected_types) {
   EXPECT_FALSE(intent_filter->activity_name.has_value());
   EXPECT_FALSE(intent_filter->activity_label.has_value());
 
@@ -116,34 +117,36 @@
   {
     const Condition& condition = *intent_filter->conditions[1];
     EXPECT_EQ(condition.condition_type, ConditionType::kMimeType);
-    EXPECT_EQ(condition.condition_values.size(), content_types.size());
+    EXPECT_EQ(condition.condition_values.size(), filter_types.size());
 
-    for (unsigned i = 0; i < content_types.size(); ++i) {
+    for (unsigned i = 0; i < filter_types.size(); ++i) {
       EXPECT_EQ(condition.condition_values[i]->match_type,
-                PatternMatchType::kNone);
-      EXPECT_EQ(condition.condition_values[i]->value, content_types[i]);
-
-      {
-        std::vector<GURL> filesystem_urls(1U);
-        std::vector<std::string> mime_types(1U, content_types[i]);
-        EXPECT_TRUE(apps_util::IntentMatchesFilter(
-            apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types),
-            intent_filter));
-      }
-
-      {
-        std::vector<GURL> filesystem_urls(3U);
-        std::vector<std::string> mime_types(3U, content_types[i]);
-        EXPECT_TRUE(apps_util::IntentMatchesFilter(
-            apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types),
-            intent_filter));
-      }
+                PatternMatchType::kMimeType);
+      EXPECT_EQ(condition.condition_values[i]->value, filter_types[i]);
     }
   }
 
-  {
+  for (const std::string& accepted_type : accepted_types) {
+    {
+      std::vector<GURL> filesystem_urls(1U);
+      std::vector<std::string> mime_types(1U, accepted_type);
+      EXPECT_TRUE(apps_util::IntentMatchesFilter(
+          apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types),
+          intent_filter));
+    }
+
+    {
+      std::vector<GURL> filesystem_urls(3U);
+      std::vector<std::string> mime_types(3U, accepted_type);
+      EXPECT_TRUE(apps_util::IntentMatchesFilter(
+          apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types),
+          intent_filter));
+    }
+  }
+
+  for (const std::string& rejected_type : rejected_types) {
     std::vector<GURL> filesystem_urls(1U);
-    std::vector<std::string> mime_types(1U, different_content_type);
+    std::vector<std::string> mime_types(1U, rejected_type);
     EXPECT_FALSE(apps_util::IntentMatchesFilter(
         apps_util::CreateShareIntentFromFiles(filesystem_urls, mime_types),
         intent_filter));
@@ -186,10 +189,42 @@
   CheckUrlScopeFilter(target[0], app_url.GetWithoutFilename(),
                       /*different_url=*/GURL("file:///"));
 
-  const std::vector<std::string> content_types({"text/csv", "image/svg+xml"});
-  CheckShareFileFilter(
-      target[1], content_types,
-      /*different_content_type=*/"application/vnd.android.package-archive");
+  const std::vector<std::string> filter_types(
+      {"text/*", "image/svg+xml", "*/*"});
+  const std::vector<std::string> accepted_types(
+      {"text/plain", "image/svg+xml", "video/webm"});
+  const std::vector<std::string> rejected_types;  // No types are rejected.
+  CheckShareFileFilter(target[1], filter_types, accepted_types, rejected_types);
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppsBaseBrowserTest, PartialWild) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const GURL app_url(
+      embedded_test_server()->GetURL("/web_share_target/partial-wild.html"));
+
+  std::vector<mojom::IntentFilterPtr> target;
+  {
+    const web_app::WebAppRegistrar* registrar =
+        web_app::WebAppProvider::Get(browser()->profile())
+            ->registrar()
+            .AsWebAppRegistrar();
+    const web_app::AppId app_id =
+        web_app::InstallWebAppFromManifest(browser(), app_url);
+    const web_app::WebApp* web_app = registrar->GetAppById(app_id);
+    ASSERT_TRUE(web_app);
+    PopulateIntentFilters(*web_app, target);
+  }
+
+  EXPECT_EQ(target.size(), 2U);
+
+  CheckUrlScopeFilter(target[0], app_url.GetWithoutFilename(),
+                      /*different_url=*/GURL("file:///"));
+
+  const std::vector<std::string> filter_types({"image/*"});
+  const std::vector<std::string> accepted_types({"image/png", "image/svg+xml"});
+  const std::vector<std::string> rejected_types(
+      {"application/vnd.android.package-archive", "text/plain"});
+  CheckShareFileFilter(target[1], filter_types, accepted_types, rejected_types);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppsBaseBrowserTest, LaunchWithIntent) {
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index d190e8f5..d5f1e39 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -89,7 +89,8 @@
 
   // Logs an error message corresponding to |code| to the devtools console.
   void ReportStatus(InstallableStatusCode code) override {
-    LogErrorToConsole(web_contents_, code);
+    LogToConsole(web_contents_, code,
+                 blink::mojom::ConsoleMessageLevel::kError);
   }
 
   WebappInstallSource GetInstallSource(content::WebContents* web_contents,
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 9ed123b..62a53294 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -536,6 +536,11 @@
         <include name="IDR_MACHINE_LEARNING_INTERNALS_TIME_MOJO_JS" file="${root_gen_dir}\mojo/public/mojom/base/time.mojom-lite.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_MACHINE_LEARNING_INTERNALS_UTILS_JS" file="resources\chromeos\machine_learning\machine_learning_internals_utils.js" type="BINDATA" />
       </if>
+      <if expr="chromeos">
+        <include name="IDR_EMOJI_PICKER_HTML" file="resources\chromeos\emoji_picker\emoji_picker.html" type="BINDATA" />
+        <include name="IDR_EMOJI_PICKER_CSS" file="resources\chromeos\emoji_picker\emoji_picker.css" type="BINDATA" />
+        <include name="IDR_EMOJI_PICKER_JS" file="resources\chromeos\emoji_picker\emoji_picker.js" type="BINDATA" />
+      </if>
     </includes>
   </release>
 </grit>
diff --git a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
index f8787b3..67752dc 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
@@ -165,7 +165,6 @@
   SET_STRING("SYNC_FILE_NUMBER", IDS_FILE_BROWSER_SYNC_FILE_NUMBER);
   SET_STRING("SYNC_MISC_ERROR", IDS_FILE_BROWSER_SYNC_MISC_ERROR);
   SET_STRING("SYNC_NO_SERVER_SPACE", IDS_FILE_BROWSER_SYNC_NO_SERVER_SPACE);
-  SET_STRING("SYNC_PROGRESS_SUMMARY", IDS_FILE_BROWSER_SYNC_PROGRESS_SUMMARY);
   SET_STRING("SYNC_SERVICE_UNAVAILABLE_ERROR",
              IDS_FILE_BROWSER_SYNC_SERVICE_UNAVAILABLE_ERROR);
 }
@@ -486,7 +485,6 @@
   SET_STRING("COPY_FILE_NAME_LONG", IDS_FILE_BROWSER_COPY_FILE_NAME_LONG);
   SET_STRING("COPY_ITEMS_REMAINING_LONG",
              IDS_FILE_BROWSER_COPY_ITEMS_REMAINING_LONG);
-  SET_STRING("COPY_PROGRESS_SUMMARY", IDS_FILE_BROWSER_COPY_PROGRESS_SUMMARY);
   SET_STRING("COPY_SOURCE_NOT_FOUND_ERROR",
              IDS_FILE_BROWSER_COPY_SOURCE_NOT_FOUND_ERROR);
   SET_STRING("COPY_TARGET_EXISTS_ERROR",
@@ -503,8 +501,6 @@
   SET_STRING("DELETE_ERROR", IDS_FILE_BROWSER_DELETE_ERROR);
   SET_STRING("DELETE_FILE_NAME", IDS_FILE_BROWSER_DELETE_FILE_NAME);
   SET_STRING("DELETE_ITEMS_REMAINING", IDS_FILE_BROWSER_DELETE_ITEMS_REMAINING);
-  SET_STRING("DELETE_PROGRESS_SUMMARY",
-             IDS_FILE_BROWSER_DELETE_PROGRESS_SUMMARY);
   SET_STRING("DEVICE_HARD_UNPLUGGED_MESSAGE",
              IDS_DEVICE_HARD_UNPLUGGED_MESSAGE);
   SET_STRING("DEVICE_HARD_UNPLUGGED_TITLE", IDS_DEVICE_HARD_UNPLUGGED_TITLE);
@@ -549,9 +545,6 @@
   SET_STRING("ERROR_LONG_NAME", IDS_FILE_BROWSER_ERROR_LONG_NAME);
   SET_STRING("ERROR_EXTERNAL_DRIVE_LONG_NAME",
              IDS_FILE_BROWSER_ERROR_EXTERNAL_DRIVE_LONG_NAME);
-  SET_STRING("ERROR_PROGRESS_SUMMARY", IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY);
-  SET_STRING("ERROR_PROGRESS_SUMMARY_PLURAL",
-             IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY_PLURAL);
   SET_STRING("ERROR_RENAMING", IDS_FILE_BROWSER_ERROR_RENAMING);
   SET_STRING("ERROR_RESERVED_NAME", IDS_FILE_BROWSER_ERROR_RESERVED_NAME);
   SET_STRING("ERROR_WHITESPACE_NAME", IDS_FILE_BROWSER_ERROR_WHITESPACE_NAME);
@@ -730,7 +723,6 @@
   SET_STRING("MOVE_FILE_NAME_LONG", IDS_FILE_BROWSER_MOVE_FILE_NAME_LONG);
   SET_STRING("MOVE_ITEMS_REMAINING_LONG",
              IDS_FILE_BROWSER_MOVE_ITEMS_REMAINING_LONG);
-  SET_STRING("MOVE_PROGRESS_SUMMARY", IDS_FILE_BROWSER_MOVE_PROGRESS_SUMMARY);
   SET_STRING("MOVE_SOURCE_NOT_FOUND_ERROR",
              IDS_FILE_BROWSER_MOVE_SOURCE_NOT_FOUND_ERROR);
   SET_STRING("MOVE_TARGET_EXISTS_ERROR",
@@ -967,8 +959,6 @@
              IDS_FILE_BROWSER_TIME_REMAINING_ESTIMATE_2);
   SET_STRING("TIME_TODAY", IDS_FILE_BROWSER_TIME_TODAY);
   SET_STRING("TIME_YESTERDAY", IDS_FILE_BROWSER_TIME_YESTERDAY);
-  SET_STRING("TRANSFER_PROGRESS_SUMMARY",
-             IDS_FILE_BROWSER_TRANSFER_PROGRESS_SUMMARY);
   SET_STRING("TYPE_COLUMN_LABEL", IDS_FILE_BROWSER_TYPE_COLUMN_LABEL);
   SET_STRING("UNDO_DELETE_ACTION_LABEL",
              IDS_FILE_BROWSER_UNDO_DELETE_ACTION_LABEL);
@@ -988,7 +978,6 @@
   SET_STRING("ZIP_FILESYSTEM_ERROR", IDS_FILE_BROWSER_ZIP_FILESYSTEM_ERROR);
   SET_STRING("ZIP_FILE_NAME", IDS_FILE_BROWSER_ZIP_FILE_NAME);
   SET_STRING("ZIP_ITEMS_REMAINING", IDS_FILE_BROWSER_ZIP_ITEMS_REMAINING);
-  SET_STRING("ZIP_PROGRESS_SUMMARY", IDS_FILE_BROWSER_ZIP_PROGRESS_SUMMARY);
   SET_STRING("ZIP_SELECTION_BUTTON_LABEL",
              IDS_FILE_BROWSER_ZIP_SELECTION_BUTTON_LABEL);
   SET_STRING("ZIP_TARGET_EXISTS_ERROR",
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine.cc b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
index 564339db..97e98db 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine.cc
@@ -15,8 +15,10 @@
 #include "chrome/browser/chromeos/input_method/autocorrect_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "ui/base/emoji/emoji_panel_helper.h"
 #include "ui/base/ime/chromeos/ime_bridge.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
 
@@ -204,6 +206,10 @@
     // TODO(b/147709499): A better way to cleanup all.
     remote_manager_.reset();
   }
+  if (base::FeatureList::IsEnabled(chromeos::features::kImeSystemEmojiPicker)) {
+    ui::SetShowEmojiKeyboardCallback(
+        base::BindRepeating(&EmojiPickerDialog::Show));
+  }
   base_observer_->OnActivate(engine_id);
 }
 void NativeInputMethodEngine::ImeObserver::ProcessMessage(
diff --git a/chrome/browser/chromeos/input_method/ui/DEPS b/chrome/browser/chromeos/input_method/ui/DEPS
index 86a75d26..d4012a6 100644
--- a/chrome/browser/chromeos/input_method/ui/DEPS
+++ b/chrome/browser/chromeos/input_method/ui/DEPS
@@ -1,3 +1,3 @@
 include_rules = [
   "+chrome/app/vector_icons"
-]
\ No newline at end of file
+]
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
index f323486..14fcce18 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
@@ -608,6 +608,10 @@
 
 // Attestation enrollment.
 IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, Attestation) {
+  // Even though the server would allow device attributes update, Chrome OS will
+  // not attempt that for attestation enrollment.
+  policy_server_.SetUpdateDeviceAttributesPermission(true);
+
   AllowlistSimpleChallengeSigningKey();
   policy_server_.SetFakeAttestationFlow();
   EXPECT_TRUE(policy_server_.SetDeviceStateRetrievalResponse(
diff --git a/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc b/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
index 3c25051..c1f7af96 100644
--- a/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
+++ b/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
@@ -286,6 +286,19 @@
 
 void EnterpriseEnrollmentHelperImpl::GetDeviceAttributeUpdatePermission() {
   DCHECK(auth_data_);
+  if (!auth_data_->has_oauth_token()) {
+    // Checking whether the device attributes can be updated requires knowning
+    // which user is performing enterprise enrollment, because the permission is
+    // tied to a user.
+    // For enterprise enrollment authorized by attestation or an enrollment
+    // token, the current user is unknown.
+    // A possible follow-up (tracked in https://crbug.com/942013) will be to
+    // allow the first affiliated user that signs in and has the permission to
+    // edit device attributes.
+    OnDeviceAttributeUpdatePermission(/*granted=*/false);
+    return;
+  }
+
   policy::BrowserPolicyConnectorChromeOS* connector =
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
   // Don't update device attributes for Active Directory management.
diff --git a/chrome/browser/dev_ui_browser_resources.grd b/chrome/browser/dev_ui_browser_resources.grd
index 85397ea..ad004d2 100644
--- a/chrome/browser/dev_ui_browser_resources.grd
+++ b/chrome/browser/dev_ui_browser_resources.grd
@@ -52,8 +52,10 @@
       <include name="IDR_MEDIA_HISTORY_STORE_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\media\history\media_history_store.mojom-lite.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_MEMORY_INTERNALS_HTML" file="resources\memory_internals\memory_internals.html" type="BINDATA" />
       <include name="IDR_MEMORY_INTERNALS_JS" file="resources\memory_internals\memory_internals.js" type="BINDATA" />
+      <include name="IDR_PREDICTORS_AUTOCOMPLETE_ACTION_PREDICTOR_JS" file="resources\predictors\autocomplete_action_predictor.js" type="BINDATA" />
       <include name="IDR_PREDICTORS_HTML" file="resources\predictors\predictors.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_PREDICTORS_JS" file="resources\predictors\predictors.js" flattenhtml="true" type="BINDATA" />
+      <include name="IDR_PREDICTORS_JS" file="resources\predictors\predictors.js" type="BINDATA" />
+      <include name="IDR_PREDICTORS_RESOURCE_PREFETCH_PREDICTOR_JS" file="resources\predictors\resource_prefetch_predictor.js" type="BINDATA" />
       <include name="IDR_MEDIA_SESSION_MOJOM_LITE_JS" file="${root_gen_dir}\services\media_session\public\mojom\media_session.mojom-lite.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_UI_GEOMETRY_MOJOM_LITE_JS" file="${root_gen_dir}\ui\gfx\geometry\mojom\geometry.mojom-lite.js" use_base_dir="false" type="BINDATA" />
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 0ea7dedc..20b7925f 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1433,6 +1433,11 @@
     "expiry_milestone": 91
   },
   {
+    "name": "enable-cros-ime-system-emoji-picker",
+    "owners": [ "essential-inputs-team@google.com" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "enable-cros-language-settings-update",
     "owners": [ "essential-inputs-team@google.com" ],
     "expiry_milestone": 90
@@ -1496,6 +1501,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "enable-desktop-pwas-elided-extensions-menu",
+    "owners": [ "alancutter@chromium.org", "desktop-pwas-team@google.com" ],
+    "expiry_milestone": 91
+  },
+  {
     "name": "enable-desktop-pwas-local-updating",
     "owners": [ "desktop-pwas-team@google.com" ],
     "expiry_milestone": 88
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ce79fd4..773cf6a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -662,6 +662,12 @@
     "Enable installed PWAs to include a menu of shortcuts associated with the "
     "app icon in the taskbar on Windows, or the dock on macOS or Linux.";
 
+const char kDesktopPWAsElidedExtensionsMenuName[] =
+    "Desktop PWAs elided extensions menu";
+const char kDesktopPWAsElidedExtensionsMenuDescription[] =
+    "Moves the Extensions \"puzzle piece\" icon from the title bar into the "
+    "app menu for web app windows.";
+
 const char kDesktopPWAsTabStripName[] = "Desktop PWA tab strips";
 const char kDesktopPWAsTabStripDescription[] =
     "Experimental UI for exploring what PWA windows would look like with a tab "
@@ -2221,7 +2227,7 @@
 
 const char kSuggestedContentToggleName[] = "Enable Suggested Content toggle";
 const char kSuggestedContentToggleDescription[] =
-    "Enables a settings UI toggle that controls Suggested Content status. Also"
+    "Enables a settings UI toggle that controls Suggested Content status. Also "
     "enables a corresponding notice in the Launcher about Suggested Content.";
 
 const char kSuggestionsWithSubStringMatchName[] =
@@ -4351,6 +4357,11 @@
     "Controls whether ChromeOS system IME works with the NaCl decoders or "
     "the decoders loaded in the IME service.";
 
+const char kImeSystemEmojiPickerName[] = "System emoji picker";
+const char kImeSystemEmojiPickerDescription[] =
+    "Controls whether a System emoji picker, or the virtual keyboard is used "
+    "for inserting emoji.";
+
 const char kIntentHandlingSharingName[] = "Intent handling for sharing";
 const char kIntentHandlingSharingDescription[] =
     "Support sharing in Chrome OS intent handling.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index daa901b..b465687 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -405,6 +405,9 @@
 extern const char kDesktopPWAsAppIconShortcutsMenuName[];
 extern const char kDesktopPWAsAppIconShortcutsMenuDescription[];
 
+extern const char kDesktopPWAsElidedExtensionsMenuName[];
+extern const char kDesktopPWAsElidedExtensionsMenuDescription[];
+
 extern const char kDesktopPWAsTabStripName[];
 extern const char kDesktopPWAsTabStripDescription[];
 
@@ -2539,6 +2542,9 @@
 extern const char kImeServiceDecoderName[];
 extern const char kImeServiceDecoderDescription[];
 
+extern const char kImeSystemEmojiPickerName[];
+extern const char kImeSystemEmojiPickerDescription[];
+
 extern const char kIntentHandlingSharingName[];
 extern const char kIntentHandlingSharingDescription[];
 
diff --git a/chrome/browser/installable/installable_logging.cc b/chrome/browser/installable/installable_logging.cc
index 4f940c7..8cf42596e 100644
--- a/chrome/browser/installable/installable_logging.cc
+++ b/chrome/browser/installable/installable_logging.cc
@@ -65,6 +65,9 @@
 static const char kManifestDisplayOverrideNotSupportedMessage[] =
     "Manifest contains 'display_override' field, and the first supported "
     "display mode must be one of 'standalone', 'fullscreen', or 'minimal-ui'";
+static const char kWarnNotOfflineCapable[] =
+    "Page does not work offline. The page will not be regarded as installable "
+    "after M93, stable release August 2021.";
 
 static const char kNotFromSecureOriginId[] = "not-from-secure-origin";
 static const char kNoManifestId[] = "no-manifest";
@@ -98,6 +101,7 @@
 static const char kManifestLocationChangedId[] = "manifest-location-changed";
 static const char kManifestDisplayOverrideNotSupportedId[] =
     "manifest-display-override-not-supported";
+static const char kWarnNotOfflineCapableId[] = "warn-not-offline-capable";
 
 const std::string& GetMessagePrefix() {
   static base::NoDestructor<std::string> message_prefix(
@@ -202,6 +206,9 @@
     case MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED:
       message = kManifestDisplayOverrideNotSupportedMessage;
       break;
+    case WARN_NOT_OFFLINE_CAPABLE:
+      message = kWarnNotOfflineCapable;
+      break;
   }
 
   return message;
@@ -307,14 +314,18 @@
     case MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED:
       error_id = kManifestDisplayOverrideNotSupportedId;
       break;
+    case WARN_NOT_OFFLINE_CAPABLE:
+      error_id = kWarnNotOfflineCapableId;
+      break;
   }
   error.error_id = error_id;
   error.installability_error_arguments = error_arguments;
   return error;
 }
 
-void LogErrorToConsole(content::WebContents* web_contents,
-                       InstallableStatusCode code) {
+void LogToConsole(content::WebContents* web_contents,
+                  InstallableStatusCode code,
+                  blink::mojom::ConsoleMessageLevel level) {
   if (!web_contents)
     return;
 
@@ -324,5 +335,5 @@
     return;
 
   web_contents->GetMainFrame()->AddMessageToConsole(
-      blink::mojom::ConsoleMessageLevel::kError, GetMessagePrefix() + message);
+      level, GetMessagePrefix() + message);
 }
diff --git a/chrome/browser/installable/installable_logging.h b/chrome/browser/installable/installable_logging.h
index 02be95e..96f2384f 100644
--- a/chrome/browser/installable/installable_logging.h
+++ b/chrome/browser/installable/installable_logging.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
+
 namespace content {
 struct InstallabilityError;
 class WebContents;
@@ -58,6 +60,7 @@
   PREFER_RELATED_APPLICATIONS_SUPPORTED_ONLY_BETA_STABLE = 37,
   MANIFEST_URL_CHANGED = 38,
   MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED = 39,
+  WARN_NOT_OFFLINE_CAPABLE = 40,
   MAX_ERROR_CODE,
 };
 
@@ -68,7 +71,8 @@
 
 // Logs a message associated with |code| to the devtools console attached to
 // |web_contents|. Does nothing if |web_contents| is nullptr.
-void LogErrorToConsole(content::WebContents* web_contents,
-                       InstallableStatusCode code);
+void LogToConsole(content::WebContents* web_contents,
+                  InstallableStatusCode code,
+                  blink::mojom::ConsoleMessageLevel level);
 
 #endif  // CHROME_BROWSER_INSTALLABLE_INSTALLABLE_LOGGING_H_
diff --git a/chrome/browser/installable/installable_manager.cc b/chrome/browser/installable/installable_manager.cc
index acc0233..ca2173d 100644
--- a/chrome/browser/installable/installable_manager.cc
+++ b/chrome/browser/installable/installable_manager.cc
@@ -769,8 +769,15 @@
   InstallableMetrics::RecordCheckServiceWorkerStatus(
       InstallableMetrics::ConvertFromOfflineCapability(capability));
 
-  if (!enforce_offline_capability)
+  if (!enforce_offline_capability) {
+    if (capability == content::OfflineCapability::kUnsupported) {
+      LogToConsole(web_contents(), WARN_NOT_OFFLINE_CAPABLE,
+                   blink::mojom::ConsoleMessageLevel::kWarning);
+    }
+    // No enforcement means that we are just recording metrics and logging a
+    // warning.
     return;
+  }
 
   switch (capability) {
     case content::OfflineCapability::kSupported:
diff --git a/chrome/browser/media/webrtc/webrtc_pan_tilt_zoom_browsertest.cc b/chrome/browser/media/webrtc/webrtc_pan_tilt_zoom_browsertest.cc
index 06d2a3eb..ff8bee8 100644
--- a/chrome/browser/media/webrtc/webrtc_pan_tilt_zoom_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_pan_tilt_zoom_browsertest.cc
@@ -39,11 +39,6 @@
     : public WebRtcTestBase,
       public testing::WithParamInterface<PermissionTestConfig> {
  public:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
-                                    "MediaCapturePanTilt");
-  }
-
   void SetUpInProcessBrowserTestFixture() override {
     DetectErrorsInJavaScript();
   }
@@ -415,9 +410,8 @@
           bool /* IsPanTiltZoomSupported() */> {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kEnableBlinkFeatures,
-        "MediaCapturePanTilt,PermissionsRequestRevoke");
+    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
+                                    "PermissionsRequestRevoke");
   }
 
   bool IsPanTiltZoomSupported() const { return GetParam(); }
@@ -472,9 +466,8 @@
 class WebRtcPanTiltZoomCameraDevicesBrowserTest : public WebRtcTestBase {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kEnableBlinkFeatures,
-        "MediaCapturePanTilt,PermissionsRequestRevoke");
+    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
+                                    "PermissionsRequestRevoke");
   }
 
   void SetVideoCaptureDevice(bool pan_supported,
diff --git a/chrome/browser/predictors/loading_predictor_tab_helper.cc b/chrome/browser/predictors/loading_predictor_tab_helper.cc
index 5e3db6b..e6d88799 100644
--- a/chrome/browser/predictors/loading_predictor_tab_helper.cc
+++ b/chrome/browser/predictors/loading_predictor_tab_helper.cc
@@ -62,6 +62,7 @@
     case network::mojom::RequestDestination::kSharedWorker:
     case network::mojom::RequestDestination::kTrack:
     case network::mojom::RequestDestination::kVideo:
+    case network::mojom::RequestDestination::kWebBundle:
     case network::mojom::RequestDestination::kWorker:
     case network::mojom::RequestDestination::kXslt:
       return net::LOWEST;
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
index 5d4882fa..eb9995aa 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
@@ -4,80 +4,18 @@
 
 #include "chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h"
 
-#include <vector>
-
-#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/net/variations_http_headers.h"
 #include "content/public/browser/client_hints.h"
 #include "content/public/browser/frame_accept_header.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/url_loader_throttles.h"
-#include "content/public/browser/web_contents.h"
 #include "content/public/common/content_constants.h"
 #include "net/base/load_flags.h"
-#include "net/cookies/site_for_cookies.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/resource_request.h"
-#include "services/network/public/mojom/fetch_api.mojom.h"
-#include "third_party/blink/public/common/loader/url_loader_throttle.h"
-#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
-#include "url/origin.h"
-
-namespace {
-
-// A custom URLLoaderThrottle delegate that is very sensitive. Anything that
-// would delay or cancel the request is treated the same, which would prevent
-// the prefetch request.
-class CheckForCancelledOrPausedDelegate
-    : public blink::URLLoaderThrottle::Delegate {
- public:
-  CheckForCancelledOrPausedDelegate() = default;
-  ~CheckForCancelledOrPausedDelegate() override = default;
-
-  CheckForCancelledOrPausedDelegate(const CheckForCancelledOrPausedDelegate&) =
-      delete;
-  CheckForCancelledOrPausedDelegate& operator=(
-      const CheckForCancelledOrPausedDelegate&) = delete;
-
-  // URLLoaderThrottle::Delegate:
-  void CancelWithError(int error_code,
-                       base::StringPiece custom_reason) override {
-    cancelled_or_paused_ = true;
-  }
-
-  void Resume() override {}
-
-  void PauseReadingBodyFromNet() override { cancelled_or_paused_ = true; }
-
-  void RestartWithFlags(int additional_load_flags) override {
-    cancelled_or_paused_ = true;
-  }
-
-  void RestartWithURLResetAndFlags(int additional_load_flags) override {
-    cancelled_or_paused_ = true;
-  }
-
-  void RestartWithURLResetAndFlagsNow(int additional_load_flags) override {
-    cancelled_or_paused_ = true;
-  }
-
-  void RestartWithModifiedHeadersNow(
-      const net::HttpRequestHeaders& modified_headers) override {
-    cancelled_or_paused_ = true;
-  }
-
-  bool cancelled_or_paused() const { return cancelled_or_paused_; }
-
- private:
-  bool cancelled_or_paused_ = false;
-};
-
-}  // namespace
 
 BaseSearchPrefetchRequest::BaseSearchPrefetchRequest(
     const GURL& prefetch_url,
@@ -87,7 +25,7 @@
 
 BaseSearchPrefetchRequest::~BaseSearchPrefetchRequest() = default;
 
-bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
+void BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
   net::NetworkTrafficAnnotationTag network_traffic_annotation =
       net::DefineNetworkTrafficAnnotation("search_prefetch_service", R"(
         semantics {
@@ -123,8 +61,6 @@
           }
         })");
 
-  url::Origin prefetch_origin = url::Origin::Create(prefetch_url_);
-
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->load_flags |= net::LOAD_PREFETCH;
   resource_request->url = prefetch_url_;
@@ -134,26 +70,8 @@
   resource_request->report_raw_headers = true;
   resource_request->credentials_mode =
       network::mojom::CredentialsMode::kInclude;
-  resource_request->method = "GET";
-  resource_request->mode = network::mojom::RequestMode::kNavigate;
-  resource_request->site_for_cookies =
-      net::SiteForCookies::FromUrl(prefetch_url_);
-  resource_request->destination = network::mojom::RequestDestination::kDocument;
-  resource_request->resource_type =
-      static_cast<int>(blink::mojom::ResourceType::kMainFrame);
-  resource_request->trusted_params = network::ResourceRequest::TrustedParams();
-  // We don't handle redirects, so |kOther| makes sense here.
-  resource_request->trusted_params->isolation_info = net::IsolationInfo::Create(
-      net::IsolationInfo::RequestType::kOther, prefetch_origin, prefetch_origin,
-      resource_request->site_for_cookies);
-  resource_request->referrer_policy = net::ReferrerPolicy::NO_REFERRER;
-
-  // Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational
-  // requests, as described in
-  // https://w3c.github.io/webappsec/specs/upgrade/#feature-detect
-  resource_request->headers.SetHeader("Upgrade-Insecure-Requests", "1");
-  resource_request->headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
-                                      GetUserAgent());
+  variations::AppendVariationsHeaderUnknownSignedIn(
+      prefetch_url_, variations::InIncognito::kNo, resource_request.get());
   resource_request->headers.SetHeader(content::kCorsExemptPurposeHeaderName,
                                       "prefetch");
   resource_request->headers.SetHeader(
@@ -168,37 +86,13 @@
       profile->GetClientHintsControllerDelegate(),
       /*is_ua_override_on=*/false, js_enabled);
 
-  // Before sending out the request, allow throttles to modify the request (not
-  // the URL). The rest of the URL Loader throttle calls are captured in the
-  // navigation stack. Headers can be added by throttles at this point, which we
-  // want to capture.
-  auto wc_getter =
-      base::BindRepeating([]() -> content::WebContents* { return nullptr; });
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
-      content::CreateContentBrowserURLLoaderThrottles(
-          *resource_request, profile, std::move(wc_getter),
-          /*navigation_ui_data=*/nullptr,
-          content::RenderFrameHost::kNoFrameTreeNodeId);
-
-  bool should_defer = false;
-  for (auto& throttle : throttles) {
-    CheckForCancelledOrPausedDelegate cancel_or_pause_delegate;
-    throttle->set_delegate(&cancel_or_pause_delegate);
-    throttle->WillStartRequest(resource_request.get(), &should_defer);
-    // Make sure throttles are deleted before |cancel_or_pause_delegate| in case
-    // they call into the delegate in the destructor.
-    throttle.reset();
-    if (should_defer || resource_request->url != prefetch_url_ ||
-        cancel_or_pause_delegate.cancelled_or_paused()) {
-      return false;
-    }
-  }
+  // TODO(ryansturm): Find other headers that may need to be set.
+  // https://crbug.com/1138648
 
   current_status_ = SearchPrefetchStatus::kInFlight;
 
   StartPrefetchRequestInternal(profile, std::move(resource_request),
                                network_traffic_annotation);
-  return true;
 }
 
 void BaseSearchPrefetchRequest::CancelPrefetch() {
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
index 8abc563c..3316725 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
@@ -49,10 +49,8 @@
       delete;
 
   // Starts the network request to prefetch |prefetch_url_|. Sets various fields
-  // on a resource request and calls |StartPrefetchRequestInternal()|. Returns
-  // |false| if the request is not started (i.e., it would be deferred by
-  // throttles).
-  bool StartPrefetchRequest(Profile* profile);
+  // on a resource request and calls |StartPrefetchRequestInternal()|.
+  void StartPrefetchRequest(Profile* profile);
 
   // Marks a prefetch as canceled and stops any ongoing fetch.
   void CancelPrefetch();
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
index 27d93ca9..f018dca 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service.cc
@@ -127,9 +127,6 @@
   }
 
   DCHECK(prefetch_request);
-  if (!prefetch_request->StartPrefetchRequest(profile_)) {
-    return false;
-  }
 
   prefetches_.emplace(search_terms, std::move(prefetch_request));
   prefetches_[search_terms]->StartPrefetchRequest(profile_);
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 bd81b3d..2203c62 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -59,8 +59,6 @@
 constexpr char kOmniboxSuggestNonPrefetchQuery[] = "puffins";
 constexpr char kLoadInSubframe[] = "/load_in_subframe";
 constexpr char kClientHintsURL[] = "/accept_ch_with_lifetime.html";
-constexpr char kThrottleHeader[] = "porgs-header";
-constexpr char kThrottleHeaderValue[] = "porgs-header-value";
 }  // namespace
 
 // A response that hangs after serving the start of the response.
@@ -75,99 +73,6 @@
   }
 };
 
-// A delegate to cancel prefetch requests by setting |defer| to true.
-class DeferringThrottle : public blink::URLLoaderThrottle {
- public:
-  DeferringThrottle() = default;
-  ~DeferringThrottle() override = default;
-
-  void WillStartRequest(network::ResourceRequest* request,
-                        bool* defer) override {
-    *defer = true;
-  }
-};
-
-class ThrottleAllContentBrowserClient : public ChromeContentBrowserClient {
- public:
-  ThrottleAllContentBrowserClient() = default;
-  ~ThrottleAllContentBrowserClient() override = default;
-
-  // ContentBrowserClient overrides:
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  CreateURLLoaderThrottles(
-      const network::ResourceRequest& request,
-      content::BrowserContext* browser_context,
-      const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-      content::NavigationUIData* navigation_ui_data,
-      int frame_tree_node_id) override {
-    std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
-    throttles.push_back(std::make_unique<DeferringThrottle>());
-    return throttles;
-  }
-};
-
-// A delegate to cancel prefetch requests by calling cancel on |delegate_|.
-class CancellingThrottle : public blink::URLLoaderThrottle {
- public:
-  CancellingThrottle() = default;
-  ~CancellingThrottle() override = default;
-
-  void WillStartRequest(network::ResourceRequest* request,
-                        bool* defer) override {
-    delegate_->CancelWithError(net::ERR_ABORTED);
-  }
-};
-
-class CancelAllContentBrowserClient : public ChromeContentBrowserClient {
- public:
-  CancelAllContentBrowserClient() = default;
-  ~CancelAllContentBrowserClient() override = default;
-
-  // ContentBrowserClient overrides:
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  CreateURLLoaderThrottles(
-      const network::ResourceRequest& request,
-      content::BrowserContext* browser_context,
-      const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-      content::NavigationUIData* navigation_ui_data,
-      int frame_tree_node_id) override {
-    std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
-    throttles.push_back(std::make_unique<CancellingThrottle>());
-    return throttles;
-  }
-};
-
-// A delegate to add a custom header to prefetches.
-class AddHeaderModifyingThrottle : public blink::URLLoaderThrottle {
- public:
-  AddHeaderModifyingThrottle() = default;
-  ~AddHeaderModifyingThrottle() override = default;
-
-  void WillStartRequest(network::ResourceRequest* request,
-                        bool* defer) override {
-    request->headers.SetHeader(kThrottleHeader, kThrottleHeaderValue);
-  }
-};
-
-class AddHeaderContentBrowserClient : public ChromeContentBrowserClient {
- public:
-  AddHeaderContentBrowserClient() = default;
-  ~AddHeaderContentBrowserClient() override = default;
-
-  // ContentBrowserClient overrides:
-  std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
-  CreateURLLoaderThrottles(
-      const network::ResourceRequest& request,
-      content::BrowserContext* browser_context,
-      const base::RepeatingCallback<content::WebContents*()>& wc_getter,
-      content::NavigationUIData* navigation_ui_data,
-      int frame_tree_node_id) override {
-    std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
-    throttles.push_back(std::make_unique<AddHeaderModifyingThrottle>());
-    return throttles;
-  }
-};
-
 class SearchPrefetchBaseBrowserTest : public InProcessBrowserTest {
  public:
   SearchPrefetchBaseBrowserTest() {
@@ -633,9 +538,6 @@
   EXPECT_EQ(1u, search_server_prefetch_request_count());
   // Make sure we don't get client hints headers by default.
   EXPECT_FALSE(base::Contains(headers, "viewport-width"));
-  EXPECT_TRUE(base::Contains(headers, "User-Agent"));
-  ASSERT_TRUE(base::Contains(headers, "Upgrade-Insecure-Requests"));
-  EXPECT_TRUE(base::Contains(headers["Upgrade-Insecure-Requests"], "1"));
 
   prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
       base::ASCIIToUTF16(search_terms));
@@ -643,79 +545,6 @@
   EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
 }
 
-IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
-                       PrefetchThrottled) {
-  ThrottleAllContentBrowserClient browser_client;
-  auto* old_client = content::SetBrowserClientForTesting(&browser_client);
-  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));
-  auto prefetch_status =
-      search_prefetch_service->GetSearchPrefetchStatusForTesting(
-          base::ASCIIToUTF16(search_terms));
-  EXPECT_FALSE(prefetch_status.has_value());
-  content::SetBrowserClientForTesting(old_client);
-}
-
-IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
-                       PrefetchCancelledByThrottle) {
-  CancelAllContentBrowserClient browser_client;
-  auto* old_client = content::SetBrowserClientForTesting(&browser_client);
-  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));
-  auto prefetch_status =
-      search_prefetch_service->GetSearchPrefetchStatusForTesting(
-          base::ASCIIToUTF16(search_terms));
-  EXPECT_FALSE(prefetch_status.has_value());
-  content::SetBrowserClientForTesting(old_client);
-}
-
-IN_PROC_BROWSER_TEST_P(SearchPrefetchServiceEnabledBrowserTest,
-                       PrefetchThrottleAddsHeader) {
-  AddHeaderContentBrowserClient browser_client;
-  auto* old_client = content::SetBrowserClientForTesting(&browser_client);
-  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));
-
-  auto headers = search_server_requests()[0].headers;
-  EXPECT_EQ(1u, search_server_requests().size());
-  ASSERT_TRUE(base::Contains(headers, kThrottleHeader));
-  EXPECT_TRUE(base::Contains(headers[kThrottleHeader], kThrottleHeaderValue));
-
-  prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
-      base::ASCIIToUTF16(search_terms));
-  ASSERT_TRUE(prefetch_status.has_value());
-  EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
-  content::SetBrowserClientForTesting(old_client);
-}
-
 class HeaderObserverContentBrowserClient : public ChromeContentBrowserClient {
  public:
   HeaderObserverContentBrowserClient() = default;
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 65e541e..508dbf5 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -23,6 +23,7 @@
       "interventions_internals:closure_compile",
       "media:closure_compile",
       "memory_internals:closure_compile",
+      "predictors:closure_compile",
       "reset_password:closure_compile",
     ]
     if (is_linux || is_chromeos || is_win || is_mac) {
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index 54101250..0414861 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -48,6 +48,7 @@
     "crostini_upgrader:closure_compile",
     "edu_coexistence:closure_compile",
     "edu_login:closure_compile",
+    "emoji_picker:closure_compile",
     "emulator:closure_compile",
     "gaia_action_buttons:closure_compile",
     "internet_config_dialog:closure_compile",
diff --git a/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
new file mode 100644
index 0000000..b591e0d
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/BUILD.gn
@@ -0,0 +1,14 @@
+# 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.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_library("emoji_picker") {
+  deps = [ "//ui/webui/resources/js:load_time_data.m" ]
+}
+
+js_type_check("closure_compile") {
+  uses_js_modules = true
+  deps = [ ":emoji_picker" ]
+}
diff --git a/chrome/browser/resources/chromeos/emoji_picker/OWNERS b/chrome/browser/resources/chromeos/emoji_picker/OWNERS
new file mode 100644
index 0000000..f461dfe
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/OWNERS
@@ -0,0 +1,4 @@
+jopalmer@chromium.org
+keithlee@chrome.org
+
+# COMPONENT: OS>Inputs
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.css b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.css
new file mode 100644
index 0000000..94e11ff
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.css
@@ -0,0 +1,8 @@
+/* Copyright 2020 The Chromium Authors. All Rights Reserved.
+ * Use of this source code is governed by the Apache v2.0 license that can be
+ * found in the LICENSE file.
+ */
+
+p {
+  white-space: pre-wrap;
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
new file mode 100644
index 0000000..d588c19
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <link rel="stylesheet" href="emoji_picker.css">
+  <script type="module" src="emoji_picker.js"></script>
+</head>
+<body>
+  <h1>Emoji picker</h1>
+  <p id="welcome-message"></p>
+  <div id="emoji-picker"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
new file mode 100644
index 0000000..cab03676
--- /dev/null
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.js
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache v2.0 license that can be
+// found in the LICENSE file.
+
+import './strings.m.js';
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
+function initialize() {
+  for (const emoji of loadTimeData.getString('emoji').split(',')) {
+    const block = document.createElement('div');
+    block.innerText = emoji;
+    $('emoji-picker').appendChild(block);
+  }
+}
+
+
+document.addEventListener('DOMContentLoaded', initialize);
\ No newline at end of file
diff --git a/chrome/browser/resources/predictors/BUILD.gn b/chrome/browser/resources/predictors/BUILD.gn
new file mode 100644
index 0000000..7e80eb9
--- /dev/null
+++ b/chrome/browser/resources/predictors/BUILD.gn
@@ -0,0 +1,36 @@
+# 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.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  uses_js_modules = true
+  deps = [
+    ":autocomplete_action_predictor",
+    ":predictors",
+    ":resource_prefetch_predictor",
+  ]
+}
+
+js_library("autocomplete_action_predictor") {
+  deps = [
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
+  ]
+}
+
+js_library("predictors") {
+  deps = [
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:tabs.m",
+  ]
+}
+
+js_library("resource_prefetch_predictor") {
+  deps = [
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
+  ]
+}
diff --git a/chrome/browser/resources/predictors/autocomplete_action_predictor.js b/chrome/browser/resources/predictors/autocomplete_action_predictor.js
index 2ab2179..c0f0cdfc 100644
--- a/chrome/browser/resources/predictors/autocomplete_action_predictor.js
+++ b/chrome/browser/resources/predictors/autocomplete_action_predictor.js
@@ -2,19 +2,37 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
+/**
+ * @typedef {{
+ *   enabled: boolean,
+ *   db: !Array<!{
+ *     user_text: string,
+ *     url: string,
+ *     hit_count: number,
+ *     miss_count: number,
+ *     confidence: number,
+ *   }>
+ * }}
+ */
+let AutocompleteActionPredictorDb;
+
 /**
  * Requests the database from the backend.
  */
 function requestAutocompleteActionPredictorDb() {
-  chrome.send('requestAutocompleteActionPredictorDb');
+  sendWithPromise('requestAutocompleteActionPredictorDb')
+      .then(updateAutocompleteActionPredictorDb);
 }
 
 /**
  * Callback from backend with the database contents. Sets up some globals and
  * calls to create the UI.
- * @param {Object} database Information about AutocompleteActionPredictor
- *     including the database as a flattened list, a boolean indicating if the
- *     system is enabled and the current hit weight.
+ * @param {!AutocompleteActionPredictorDb} database Information about
+ *     AutocompleteActionPredictor including the database as a flattened list,
+ *     a boolean indicating if the system is enabled and the current hit weight.
  */
 function updateAutocompleteActionPredictorDb(database) {
   console.debug('Updating Table NAP DB');
@@ -30,13 +48,13 @@
 
 /**
  * Updates the table from the database.
- * @param {Object} database Information about AutocompleteActionPredictor
- *     including the database as a flattened list, a boolean indicating if the
- *     system is enabled and the current hit weight.
+ * @param {!AutocompleteActionPredictorDb} database Information about
+ *     AutocompleteActionPredictor including the database as a flattened list,
+ *     a boolean indicating if the system is enabled and the current hit weight.
  */
 function updateAutocompleteActionPredictorDbView(database) {
   const databaseSection = $('databaseTableBody');
-  const showEnabled = database.enabled && database.db;
+  const showEnabled = database.enabled && !!database.db;
 
   $('autocompleteActionPredictorEnabledMode').hidden = !showEnabled;
   $('autocompleteActionPredictorDisabledMode').hidden = showEnabled;
diff --git a/chrome/browser/resources/predictors/predictors.html b/chrome/browser/resources/predictors/predictors.html
index d7f1f90d..8da31103 100644
--- a/chrome/browser/resources/predictors/predictors.html
+++ b/chrome/browser/resources/predictors/predictors.html
@@ -3,22 +3,13 @@
 <head>
   <meta charset="utf-8">
   <title>Predictors</title>
-  <link rel="stylesheet" href="chrome://resources/css/list.css">
   <link rel="stylesheet" href="chrome://resources/css/tabs.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-  <link rel="stylesheet" href="chrome://resources/css/tree.css">
 
   <style>
     <include src="predictors.css">
   </style>
 
-  <!-- List stuff. -->
-  <script src="chrome://resources/js/cr.js"></script>
-  <script src="chrome://resources/js/cr/ui.js"></script>
-  <script src="chrome://resources/js/cr/ui/focus_outline_manager.js"></script>
-  <script src="chrome://resources/js/cr/ui/tabs.js"></script>
-  <script src="chrome://resources/js/assert.js"></script>
-  <script src="chrome://resources/js/util.js"></script>
 </head>
 
 <body>
@@ -36,6 +27,6 @@
       </tabpanel>
     </tabpanels>
   </tabbox>
-  <script src="predictors.js"></script>
+  <script type="module" src="predictors.js"></script>
 </body>
 </html>
diff --git a/chrome/browser/resources/predictors/predictors.js b/chrome/browser/resources/predictors/predictors.js
index 9c79a57..47a9939 100644
--- a/chrome/browser/resources/predictors/predictors.js
+++ b/chrome/browser/resources/predictors/predictors.js
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// <include src="autocomplete_action_predictor.js">
-// <include src="resource_prefetch_predictor.js">
+import './autocomplete_action_predictor.js';
+import './resource_prefetch_predictor.js';
 
-if (cr.isWindows) {
+import {isWindows} from 'chrome://resources/js/cr.m.js';
+import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+import {TabBox} from 'chrome://resources/js/cr/ui/tabs.m.js';
+
+if (isWindows) {
   document.documentElement.setAttribute('os', 'win');
 }
 
-cr.ui.decorate('tabbox', cr.ui.TabBox);
+decorate('tabbox', TabBox);
diff --git a/chrome/browser/resources/predictors/resource_prefetch_predictor.js b/chrome/browser/resources/predictors/resource_prefetch_predictor.js
index 4b7eeaa..a4db0936 100644
--- a/chrome/browser/resources/predictors/resource_prefetch_predictor.js
+++ b/chrome/browser/resources/predictors/resource_prefetch_predictor.js
@@ -2,19 +2,48 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {$} from 'chrome://resources/js/util.m.js';
+
+/**
+ * @typedef {{
+ *   enabled: boolean,
+ *   origin_db: !Array<!OriginData>
+ * }}
+ */
+let ResourcePrefetchPredictorDb;
+
+/**
+ * @typedef {{
+ *   main_frame_host: string,
+ *   origins: !Array<!{
+ *     origin: string,
+ *     number_of_hits: number,
+ *     number_of_misses: number,
+ *     consecutive_misses: number,
+ *     position: number,
+ *     always_access_network: boolean,
+ *     accessed_network: boolean,
+ *     score: number
+ *   }>
+ * }}
+ */
+let OriginData;
+
 /**
  * Requests the database from the backend.
  */
 function requestResourcePrefetchPredictorDb() {
-  chrome.send('requestResourcePrefetchPredictorDb');
+  sendWithPromise('requestResourcePrefetchPredictorDb')
+      .then(updateResourcePrefetchPredictorDb);
 }
 
 /**
  * Callback from backend with the database contents. Sets up some globals and
  * calls to create the UI.
- * @param {Object} database Information about ResourcePrefetchPredictor
- *     including the database as a flattened list, a boolean indicating if the
- *     system is enabled.
+ * @param {!ResourcePrefetchPredictorDb} database Information about
+ *     ResourcePrefetchPredictor including the database as a flattened list, a
+ *     boolean indicating if the system is enabled.
  */
 function updateResourcePrefetchPredictorDb(database) {
   updateResourcePrefetchPredictorDbView(database);
@@ -31,9 +60,9 @@
 
 /**
  * Updates the table from the database.
- * @param {Object} database Information about ResourcePrefetchPredictor
- *     including the database as a flattened list, a boolean indicating if the
- *     system is enabled and the current hit weight.
+ * @param {!ResourcePrefetchPredictorDb} database Information about
+ *     ResourcePrefetchPredictor including the database as a flattened list, a
+ *     boolean indicating if the system is enabled and the current hit weight.
  */
 function updateResourcePrefetchPredictorDbView(database) {
   if (!database.enabled) {
@@ -55,7 +84,7 @@
 /**
  * Renders the content of the predictor origin table.
  * @param {HTMLElement} body element of table to render into.
- * @param {Object} database to render.
+ * @param {!Array<!OriginData>} database to render.
  */
 function renderOriginData(body, database) {
   body.textContent = '';
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index a0c5248..97a0406 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -1370,7 +1370,9 @@
 }
 
 // Visits a page in an app window with https error and proceed:
-IN_PROC_BROWSER_TEST_F(SSLUITest, InAppTestHTTPSExpiredCertAndProceed) {
+// Disabled due to flaky failures; see https://crbug.com/1156046.
+IN_PROC_BROWSER_TEST_F(SSLUITest,
+                       DISABLED_InAppTestHTTPSExpiredCertAndProceed) {
   ASSERT_TRUE(https_server_expired_.Start());
 
   const GURL app_url = https_server_expired_.GetURL("/ssl/google.html");
diff --git a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
index b8116d600..e5ee3d89 100644
--- a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
@@ -668,6 +668,7 @@
   pdm->RecordUseOf(*cards[0]);
   std::vector<AutofillProfile*> profiles = pdm->GetServerProfiles();
   ASSERT_EQ(1uL, profiles.size());
+  // TODO(crbug.com/941498): Server profiles are not recorded.
   pdm->RecordUseOf(*profiles[0]);
 
   // Keep the same data (only change the customer data and the cloud token to
@@ -725,6 +726,7 @@
   pdm->RecordUseOf(*cards[0]);
   std::vector<AutofillProfile*> profiles = pdm->GetServerProfiles();
   ASSERT_EQ(1uL, profiles.size());
+  // TODO(crbug.com/941498): Server profiles are not recorded.
   pdm->RecordUseOf(*profiles[0]);
 
   // Update the data (also change the customer data to force the full update as
diff --git a/chrome/browser/sync/test/integration/sync_auth_test.cc b/chrome/browser/sync/test/integration/sync_auth_test.cc
index 9e2f2841..9686480 100644
--- a/chrome/browser/sync/test/integration/sync_auth_test.cc
+++ b/chrome/browser/sync/test/integration/sync_auth_test.cc
@@ -275,8 +275,7 @@
 }
 
 // Verify that ProfileSyncService fetches a new token when an old token expires.
-// Disabled due to flakiness: https://crbug.com/860200
-IN_PROC_BROWSER_TEST_F(SyncAuthTest, DISABLED_TokenExpiry) {
+IN_PROC_BROWSER_TEST_F(SyncAuthTest, TokenExpiry) {
   // Initial sync succeeds with a short lived OAuth2 Token.
   ASSERT_TRUE(SetupClients());
   GetFakeServer()->ClearHttpError();
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
index 27bc6b4d0..5d5bb52 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -524,7 +524,13 @@
             GetAppSorting(GetProfile(0))->GetAppLaunchOrdinal(app_id2));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientWebAppsBMOSyncTest, UninstallSynced) {
+// Flaky on Linux TSan (crbug.com/1108172).
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(THREAD_SANITIZER)
+#define MAYBE_UninstallSynced DISABLED_UninstallSynced
+#else
+#define MAYBE_UninstallSynced UninstallSynced
+#endif
+IN_PROC_BROWSER_TEST_F(TwoClientWebAppsBMOSyncTest, MAYBE_UninstallSynced) {
   ASSERT_TRUE(SetupSync());
   ASSERT_TRUE(AllProfilesHaveSameWebAppIds());
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/sync_file_system/local/local_file_sync_context.cc b/chrome/browser/sync_file_system/local/local_file_sync_context.cc
index 3064c6d..2cad9dc 100644
--- a/chrome/browser/sync_file_system/local/local_file_sync_context.cc
+++ b/chrome/browser/sync_file_system/local/local_file_sync_context.cc
@@ -111,7 +111,7 @@
 
 void LocalFileSyncContext::GetFileForLocalSync(
     FileSystemContext* file_system_context,
-    const LocalFileSyncInfoCallback& callback) {
+    LocalFileSyncInfoCallback callback) {
   DCHECK(file_system_context);
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
 
@@ -120,7 +120,8 @@
       base::BindOnce(&LocalFileSyncContext::GetNextURLsForSyncOnFileThread,
                      this, base::RetainedRef(file_system_context)),
       base::BindOnce(&LocalFileSyncContext::TryPrepareForLocalSync, this,
-                     base::RetainedRef(file_system_context), callback));
+                     base::RetainedRef(file_system_context),
+                     std::move(callback)));
 }
 
 void LocalFileSyncContext::ClearChangesForURL(
@@ -220,14 +221,14 @@
     FileSystemContext* file_system_context,
     const FileSystemURL& url,
     SyncMode sync_mode,
-    const LocalFileSyncInfoCallback& callback) {
+    LocalFileSyncInfoCallback callback) {
   // This is initially called on UI thread and to be relayed to IO thread.
   if (!io_task_runner_->RunsTasksInCurrentSequence()) {
     DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
     io_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&LocalFileSyncContext::PrepareForSync, this,
                                   base::RetainedRef(file_system_context), url,
-                                  sync_mode, callback));
+                                  sync_mode, std::move(callback)));
     return;
   }
   DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
@@ -240,7 +241,7 @@
       base::BindOnce(&LocalFileSyncContext::DidGetWritingStatusForSync, this,
                      base::RetainedRef(file_system_context),
                      syncable ? SYNC_STATUS_OK : SYNC_STATUS_FILE_BUSY, url,
-                     sync_mode, callback));
+                     sync_mode, std::move(callback)));
 }
 
 void LocalFileSyncContext::RegisterURLForWaitingSync(
@@ -309,10 +310,10 @@
   // Handle root directory case differently.
   if (storage::VirtualPath::IsRootPath(url.path())) {
     DCHECK(!root_delete_helper_);
-    root_delete_helper_.reset(new RootDeleteHelper(
+    root_delete_helper_ = std::make_unique<RootDeleteHelper>(
         file_system_context, sync_status(), url,
-        base::Bind(&LocalFileSyncContext::DidApplyRemoteChange,
-                   this, url, callback)));
+        base::BindOnce(&LocalFileSyncContext::DidApplyRemoteChange, this, url,
+                       callback));
     root_delete_helper_->Run();
     return;
   }
@@ -459,16 +460,16 @@
 void LocalFileSyncContext::HasPendingLocalChanges(
     FileSystemContext* file_system_context,
     const FileSystemURL& url,
-    const HasPendingLocalChangeCallback& callback) {
+    HasPendingLocalChangeCallback callback) {
   // This gets called on UI thread and relays the task on FILE thread.
   DCHECK(file_system_context);
   if (!file_system_context->default_file_task_runner()->
           RunsTasksInCurrentSequence()) {
     DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
     file_system_context->default_file_task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&LocalFileSyncContext::HasPendingLocalChanges, this,
-                       base::RetainedRef(file_system_context), url, callback));
+        FROM_HERE, base::BindOnce(&LocalFileSyncContext::HasPendingLocalChanges,
+                                  this, base::RetainedRef(file_system_context),
+                                  url, std::move(callback)));
     return;
   }
 
@@ -481,7 +482,8 @@
 
   // Fire the callback on UI thread.
   ui_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(callback, SYNC_STATUS_OK, !changes.empty()));
+      FROM_HERE,
+      base::BindOnce(std::move(callback), SYNC_STATUS_OK, !changes.empty()));
 }
 
 void LocalFileSyncContext::PromoteDemotedChanges(
@@ -784,36 +786,37 @@
 
 void LocalFileSyncContext::TryPrepareForLocalSync(
     FileSystemContext* file_system_context,
-    const LocalFileSyncInfoCallback& callback,
+    LocalFileSyncInfoCallback callback,
     std::unique_ptr<FileSystemURLQueue> urls) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(urls);
 
   if (shutdown_on_ui_) {
-    callback.Run(SYNC_STATUS_ABORT, LocalFileSyncInfo(), storage::ScopedFile());
+    std::move(callback).Run(SYNC_STATUS_ABORT, LocalFileSyncInfo(),
+                            storage::ScopedFile());
     return;
   }
 
   if (urls->empty()) {
-    callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
-                 LocalFileSyncInfo(),
-                 storage::ScopedFile());
+    std::move(callback).Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, LocalFileSyncInfo(),
+                            storage::ScopedFile());
     return;
   }
 
   const FileSystemURL url = urls->front();
   urls->pop_front();
 
-  PrepareForSync(file_system_context, url, SYNC_SNAPSHOT,
-                 base::Bind(&LocalFileSyncContext::DidTryPrepareForLocalSync,
-                            this, base::RetainedRef(file_system_context),
-                            base::Passed(&urls), callback));
+  PrepareForSync(
+      file_system_context, url, SYNC_SNAPSHOT,
+      base::BindOnce(&LocalFileSyncContext::DidTryPrepareForLocalSync, this,
+                     base::RetainedRef(file_system_context),
+                     base::Passed(&urls), std::move(callback)));
 }
 
 void LocalFileSyncContext::DidTryPrepareForLocalSync(
     FileSystemContext* file_system_context,
     std::unique_ptr<FileSystemURLQueue> remaining_urls,
-    const LocalFileSyncInfoCallback& callback,
+    LocalFileSyncInfoCallback callback,
     SyncStatusCode status,
     const LocalFileSyncInfo& sync_file_info,
     storage::ScopedFile snapshot) {
@@ -821,14 +824,14 @@
   if (status != SYNC_STATUS_FILE_BUSY) {
     PromoteDemotedChangesForURLs(file_system_context,
                                  std::move(remaining_urls));
-    callback.Run(status, sync_file_info, std::move(snapshot));
+    std::move(callback).Run(status, sync_file_info, std::move(snapshot));
     return;
   }
 
   PromoteDemotedChangesForURL(file_system_context, sync_file_info.url);
 
   // Recursively call TryPrepareForLocalSync with remaining_urls.
-  TryPrepareForLocalSync(file_system_context, callback,
+  TryPrepareForLocalSync(file_system_context, std::move(callback),
                          std::move(remaining_urls));
 }
 
@@ -882,22 +885,22 @@
     SyncStatusCode status,
     const FileSystemURL& url,
     SyncMode sync_mode,
-    const LocalFileSyncInfoCallback& callback) {
+    LocalFileSyncInfoCallback callback) {
   // This gets called on UI thread and relays the task on FILE thread.
   DCHECK(file_system_context);
   if (!file_system_context->default_file_task_runner()->
           RunsTasksInCurrentSequence()) {
     DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
     if (shutdown_on_ui_) {
-      callback.Run(
-          SYNC_STATUS_ABORT, LocalFileSyncInfo(), storage::ScopedFile());
+      std::move(callback).Run(SYNC_STATUS_ABORT, LocalFileSyncInfo(),
+                              storage::ScopedFile());
       return;
     }
     file_system_context->default_file_task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&LocalFileSyncContext::DidGetWritingStatusForSync, this,
                        base::RetainedRef(file_system_context), status, url,
-                       sync_mode, callback));
+                       sync_mode, std::move(callback)));
     return;
   }
 
@@ -936,7 +939,7 @@
       file_error != base::File::FILE_OK &&
       file_error != base::File::FILE_ERROR_NOT_FOUND) {
     status = FileErrorToSyncStatusCode(file_error);
-}
+  }
 
   DCHECK(!file_info.is_symbolic_link);
 
@@ -972,8 +975,8 @@
   }
 
   ui_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(callback, status, sync_file_info, std::move(snapshot)));
+      FROM_HERE, base::BindOnce(std::move(callback), status, sync_file_info,
+                                std::move(snapshot)));
 }
 
 void LocalFileSyncContext::ClearSyncFlagOnIOThread(
diff --git a/chrome/browser/sync_file_system/local/local_file_sync_context.h b/chrome/browser/sync_file_system/local/local_file_sync_context.h
index 7f31339d..c0dd94a 100644
--- a/chrome/browser/sync_file_system/local/local_file_sync_context.h
+++ b/chrome/browser/sync_file_system/local/local_file_sync_context.h
@@ -65,13 +65,13 @@
     SYNC_SNAPSHOT,
   };
 
-  typedef base::Callback<void(SyncStatusCode status,
-                              const LocalFileSyncInfo& sync_file_info,
-                              storage::ScopedFile snapshot)>
+  typedef base::OnceCallback<void(SyncStatusCode status,
+                                  const LocalFileSyncInfo& sync_file_info,
+                                  storage::ScopedFile snapshot)>
       LocalFileSyncInfoCallback;
 
-  typedef base::Callback<void(SyncStatusCode status,
-                              bool has_pending_changes)>
+  typedef base::OnceCallback<void(SyncStatusCode status,
+                                  bool has_pending_changes)>
       HasPendingLocalChangeCallback;
 
   LocalFileSyncContext(const base::FilePath& base_path,
@@ -96,7 +96,7 @@
   // for the file.
   // This method must be called on UI thread.
   void GetFileForLocalSync(storage::FileSystemContext* file_system_context,
-                           const LocalFileSyncInfoCallback& callback);
+                           LocalFileSyncInfoCallback callback);
 
   // TODO(kinuko): Make this private.
   // Clears all pending local changes for |url|. |done_callback| is called
@@ -142,7 +142,7 @@
   void PrepareForSync(storage::FileSystemContext* file_system_context,
                       const storage::FileSystemURL& url,
                       SyncMode sync_mode,
-                      const LocalFileSyncInfoCallback& callback);
+                      LocalFileSyncInfoCallback callback);
 
   // Registers |url| to wait until sync is enabled for |url|.
   // |on_syncable_callback| is to be called when |url| becomes syncable
@@ -179,7 +179,7 @@
   // changes.
   void HasPendingLocalChanges(storage::FileSystemContext* file_system_context,
                               const storage::FileSystemURL& url,
-                              const HasPendingLocalChangeCallback& callback);
+                              HasPendingLocalChangeCallback callback);
 
   void PromoteDemotedChanges(const GURL& origin,
                              storage::FileSystemContext* file_system_context,
@@ -256,12 +256,12 @@
   std::unique_ptr<FileSystemURLQueue> GetNextURLsForSyncOnFileThread(
       storage::FileSystemContext* file_system_context);
   void TryPrepareForLocalSync(storage::FileSystemContext* file_system_context,
-                              const LocalFileSyncInfoCallback& callback,
+                              LocalFileSyncInfoCallback callback,
                               std::unique_ptr<FileSystemURLQueue> urls);
   void DidTryPrepareForLocalSync(
       storage::FileSystemContext* file_system_context,
       std::unique_ptr<FileSystemURLQueue> remaining_urls,
-      const LocalFileSyncInfoCallback& callback,
+      LocalFileSyncInfoCallback callback,
       SyncStatusCode status,
       const LocalFileSyncInfo& sync_file_info,
       storage::ScopedFile snapshot);
@@ -278,7 +278,7 @@
       SyncStatusCode status,
       const storage::FileSystemURL& url,
       SyncMode sync_mode,
-      const LocalFileSyncInfoCallback& callback);
+      LocalFileSyncInfoCallback callback);
 
   // Helper routine for sync/writing flag handling.
   //
diff --git a/chrome/browser/sync_file_system/local/local_file_sync_service.cc b/chrome/browser/sync_file_system/local/local_file_sync_service.cc
index a8dd744..9f57df4 100644
--- a/chrome/browser/sync_file_system/local/local_file_sync_service.cc
+++ b/chrome/browser/sync_file_system/local/local_file_sync_service.cc
@@ -179,21 +179,21 @@
 }
 
 void LocalFileSyncService::SetLocalChangeProcessorCallback(
-    const GetLocalChangeProcessorCallback& get_local_change_processor) {
-  get_local_change_processor_ = get_local_change_processor;
+    GetLocalChangeProcessorCallback get_local_change_processor) {
+  get_local_change_processor_ = std::move(get_local_change_processor);
 }
 
 void LocalFileSyncService::HasPendingLocalChanges(
     const FileSystemURL& url,
-    const HasPendingLocalChangeCallback& callback) {
+    HasPendingLocalChangeCallback callback) {
   if (!base::Contains(origin_to_contexts_, url.origin().GetURL())) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(callback, SYNC_FILE_ERROR_INVALID_URL, false));
+        FROM_HERE, base::BindOnce(std::move(callback),
+                                  SYNC_FILE_ERROR_INVALID_URL, false));
     return;
   }
   sync_context_->HasPendingLocalChanges(
-      origin_to_contexts_[url.origin().GetURL()], url, callback);
+      origin_to_contexts_[url.origin().GetURL()], url, std::move(callback));
 }
 
 void LocalFileSyncService::PromoteDemotedChanges(
diff --git a/chrome/browser/sync_file_system/local/local_file_sync_service.h b/chrome/browser/sync_file_system/local/local_file_sync_service.h
index 477c9fd..b32ca114 100644
--- a/chrome/browser/sync_file_system/local/local_file_sync_service.h
+++ b/chrome/browser/sync_file_system/local/local_file_sync_service.h
@@ -51,7 +51,7 @@
       public LocalOriginChangeObserver,
       public base::SupportsWeakPtr<LocalFileSyncService> {
  public:
-  typedef base::Callback<LocalChangeProcessor*(const GURL& origin)>
+  typedef base::RepeatingCallback<LocalChangeProcessor*(const GURL& origin)>
       GetLocalChangeProcessorCallback;
 
   class Observer {
@@ -69,8 +69,8 @@
     DISALLOW_COPY_AND_ASSIGN(Observer);
   };
 
-  typedef base::Callback<void(SyncStatusCode status,
-                              bool has_pending_changes)>
+  typedef base::OnceCallback<void(SyncStatusCode status,
+                                  bool has_pending_changes)>
       HasPendingLocalChangeCallback;
 
   static std::unique_ptr<LocalFileSyncService> Create(Profile* profile);
@@ -118,12 +118,12 @@
   // TODO(kinuko): Remove this method once we stop using multiple backends
   // (crbug.com/324215), or deprecate the other if we keep doing so.
   void SetLocalChangeProcessorCallback(
-      const GetLocalChangeProcessorCallback& get_local_change_processor);
+      GetLocalChangeProcessorCallback get_local_change_processor);
 
   // Returns true via |callback| if the given file |url| has local pending
   // changes.
   void HasPendingLocalChanges(const storage::FileSystemURL& url,
-                              const HasPendingLocalChangeCallback& callback);
+                              HasPendingLocalChangeCallback callback);
 
   void PromoteDemotedChanges(const base::Closure& callback);
 
diff --git a/chrome/browser/sync_file_system/local/root_delete_helper.cc b/chrome/browser/sync_file_system/local/root_delete_helper.cc
index 1edfc7f..f4541cc 100644
--- a/chrome/browser/sync_file_system/local/root_delete_helper.cc
+++ b/chrome/browser/sync_file_system/local/root_delete_helper.cc
@@ -39,10 +39,10 @@
     storage::FileSystemContext* file_system_context,
     LocalFileSyncStatus* sync_status,
     const storage::FileSystemURL& url,
-    const FileStatusCallback& callback)
+    FileStatusCallback callback)
     : file_system_context_(file_system_context),
       url_(url),
-      callback_(callback),
+      callback_(std::move(callback)),
       sync_status_(sync_status) {
   DCHECK(file_system_context_.get());
   DCHECK(url_.is_valid());
@@ -100,8 +100,7 @@
 void RootDeleteHelper::DidOpenFileSystem(const GURL& /* root */,
                                          const std::string& /* name */,
                                          base::File::Error error) {
-  FileStatusCallback callback = callback_;
-  callback.Run(error);
+  std::move(callback_).Run(error);
 }
 
 }  // namespace sync_file_system
diff --git a/chrome/browser/sync_file_system/local/root_delete_helper.h b/chrome/browser/sync_file_system/local/root_delete_helper.h
index 31ce33a..d272b1d 100644
--- a/chrome/browser/sync_file_system/local/root_delete_helper.h
+++ b/chrome/browser/sync_file_system/local/root_delete_helper.h
@@ -30,12 +30,12 @@
 // Expected to be called on and will callback on IO thread.
 class RootDeleteHelper {
  public:
-  typedef base::Callback<void(base::File::Error)> FileStatusCallback;
+  typedef base::OnceCallback<void(base::File::Error)> FileStatusCallback;
 
   RootDeleteHelper(storage::FileSystemContext* file_system_context,
                    LocalFileSyncStatus* sync_status,
                    const storage::FileSystemURL& url,
-                   const FileStatusCallback& callback);
+                   FileStatusCallback callback);
   ~RootDeleteHelper();
 
   void Run();
diff --git a/chrome/browser/sync_file_system/sync_file_system_service.cc b/chrome/browser/sync_file_system/sync_file_system_service.cc
index 95af103..3b9d365 100644
--- a/chrome/browser/sync_file_system/sync_file_system_service.cc
+++ b/chrome/browser/sync_file_system/sync_file_system_service.cc
@@ -465,7 +465,7 @@
 
   local_service_->AddChangeObserver(local_syncer.get());
   local_service_->SetLocalChangeProcessorCallback(
-      base::Bind(&GetLocalChangeProcessorAdapter, AsWeakPtr()));
+      base::BindRepeating(&GetLocalChangeProcessorAdapter, AsWeakPtr()));
 
   remote_service_->AddServiceObserver(remote_syncer.get());
   remote_service_->AddFileStatusObserver(this);
diff --git a/chrome/browser/thumbnail/cc/thumbnail_cache.cc b/chrome/browser/thumbnail/cc/thumbnail_cache.cc
index 2225fe7..679e095 100644
--- a/chrome/browser/thumbnail/cc/thumbnail_cache.cc
+++ b/chrome/browser/thumbnail/cc/thumbnail_cache.cc
@@ -350,34 +350,34 @@
 }
 
 void ThumbnailCache::ForkToSaveAsJpeg(
-    const base::Callback<void(bool, const SkBitmap&)>& callback,
+    base::OnceCallback<void(bool, const SkBitmap&)> callback,
     int tab_id,
     bool result,
     const SkBitmap& bitmap) {
   if (result && !bitmap.isNull())
     SaveAsJpeg(tab_id, bitmap);
-  callback.Run(result, bitmap);
+  std::move(callback).Run(result, bitmap);
 }
 
 void ThumbnailCache::DecompressThumbnailFromFile(
     TabId tab_id,
-    const base::Callback<void(bool, const SkBitmap&)>&
-        post_decompress_callback) {
-  base::Callback<void(bool, const SkBitmap&)> transcoding_callback =
-      post_decompress_callback;
+    base::OnceCallback<void(bool, const SkBitmap&)> post_decompress_callback) {
+  base::OnceCallback<void(bool, const SkBitmap&)> transcoding_callback;
   if (save_jpeg_thumbnails_) {
-    transcoding_callback = base::Bind(&ThumbnailCache::ForkToSaveAsJpeg,
-                                      weak_factory_.GetWeakPtr(),
-                                      post_decompress_callback, tab_id);
+    transcoding_callback = base::BindOnce(
+        &ThumbnailCache::ForkToSaveAsJpeg, weak_factory_.GetWeakPtr(),
+        std::move(post_decompress_callback), tab_id);
+  } else {
+    transcoding_callback = std::move(post_decompress_callback);
   }
 
-  base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
-      decompress_task =
-          base::Bind(&ThumbnailCache::DecompressionTask, transcoding_callback);
+  base::OnceCallback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
+      decompress_task = base::BindOnce(&ThumbnailCache::DecompressionTask,
+                                       std::move(transcoding_callback));
 
   file_sequenced_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ThumbnailCache::ReadTask, true, tab_id, decompress_task));
+      FROM_HERE, base::BindOnce(&ThumbnailCache::ReadTask, true, tab_id,
+                                std::move(decompress_task)));
 }
 
 void ThumbnailCache::RemoveFromDisk(TabId tab_id) {
@@ -485,13 +485,13 @@
   TabId tab_id = read_queue_.front();
   read_in_progress_ = true;
 
-  base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
-      post_read_task = base::Bind(&ThumbnailCache::PostReadTask,
-                                  weak_factory_.GetWeakPtr(), tab_id);
+  base::OnceCallback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
+      post_read_task = base::BindOnce(&ThumbnailCache::PostReadTask,
+                                      weak_factory_.GetWeakPtr(), tab_id);
 
   file_sequenced_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ThumbnailCache::ReadTask, false, tab_id, post_read_task));
+      FROM_HERE, base::BindOnce(&ThumbnailCache::ReadTask, false, tab_id,
+                                std::move(post_read_task)));
 }
 
 void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
@@ -869,7 +869,7 @@
 void ThumbnailCache::ReadTask(
     bool decompress,
     TabId tab_id,
-    const base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>&
+    base::OnceCallback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
         post_read_task) {
   gfx::Size content_size;
   float scale = 0.f;
@@ -894,12 +894,13 @@
   if (decompress) {
     base::ThreadPool::PostTask(
         FROM_HERE, {base::TaskPriority::USER_VISIBLE},
-        base::BindOnce(post_read_task, std::move(compressed_data), scale,
-                       content_size));
+        base::BindOnce(std::move(post_read_task), std::move(compressed_data),
+                       scale, content_size));
   } else {
     content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(post_read_task, std::move(compressed_data),
-                                  scale, content_size));
+        FROM_HERE,
+        base::BindOnce(std::move(post_read_task), std::move(compressed_data),
+                       scale, content_size));
   }
 }
 
@@ -954,8 +955,7 @@
 }
 
 void ThumbnailCache::DecompressionTask(
-    const base::Callback<void(bool, const SkBitmap&)>&
-        post_decompression_callback,
+    base::OnceCallback<void(bool, const SkBitmap&)> post_decompression_callback,
     sk_sp<SkPixelRef> compressed_data,
     float scale,
     const gfx::Size& content_size) {
@@ -993,8 +993,8 @@
   }
 
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(post_decompression_callback, success, raw_data_small));
+      FROM_HERE, base::BindOnce(std::move(post_decompression_callback), success,
+                                raw_data_small));
 }
 
 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
diff --git a/chrome/browser/thumbnail/cc/thumbnail_cache.h b/chrome/browser/thumbnail/cc/thumbnail_cache.h
index abf716e..6dc0fc4 100644
--- a/chrome/browser/thumbnail/cc/thumbnail_cache.h
+++ b/chrome/browser/thumbnail/cc/thumbnail_cache.h
@@ -68,8 +68,7 @@
   void UpdateVisibleIds(const TabIdList& priority, TabId primary_tab_id);
   void DecompressThumbnailFromFile(
       TabId tab_id,
-      const base::Callback<void(bool, const SkBitmap&)>&
-          post_decompress_callback);
+      base::OnceCallback<void(bool, const SkBitmap&)> post_decompress_callback);
 
   // Called when resident textures were evicted, which requires paging
   // in bitmaps.
@@ -109,7 +108,7 @@
                                      std::vector<uint8_t> compressed_data);
   void SaveAsJpeg(TabId tab_id, const SkBitmap& bitmap);
   void ForkToSaveAsJpeg(
-      const base::Callback<void(bool, const SkBitmap&)>& callback,
+      base::OnceCallback<void(bool, const SkBitmap&)> callback,
       int tab_id,
       bool result,
       const SkBitmap& bitmap);
@@ -145,15 +144,14 @@
                            sk_sp<SkPixelRef> compressed_data,
                            const gfx::Size& content_size);
   static void DecompressionTask(
-      const base::Callback<void(bool, const SkBitmap&)>&
-          post_decompress_callback,
+      base::OnceCallback<void(bool, const SkBitmap&)> post_decompress_callback,
       sk_sp<SkPixelRef> compressed_data,
       float scale,
       const gfx::Size& encoded_size);
   static void ReadTask(
       bool decompress,
       TabId tab_id,
-      const base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>&
+      base::OnceCallback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
           post_read_task);
   void PostReadTask(TabId tab_id,
                     sk_sp<SkPixelRef> compressed_data,
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
index 8ef13b5..2379a85 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
@@ -21,6 +21,7 @@
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
+#include "url/origin.h"
 
 using ShowVirtualKeyboard =
     password_manager::PasswordManagerDriver::ShowVirtualKeyboard;
@@ -58,10 +59,11 @@
     view_ = TouchToFillViewFactory::Create(this);
 
   const GURL& url = driver_->GetLastCommittedURL();
-  view_->Show(url,
-              TouchToFillView::IsOriginSecure(
-                  network::IsUrlPotentiallyTrustworthy(url)),
-              credentials);
+  view_->Show(
+      url,
+      TouchToFillView::IsOriginSecure(
+          network::IsOriginPotentiallyTrustworthy(url::Origin::Create(url))),
+      credentials);
 }
 
 void TouchToFillController::OnCredentialSelected(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c5f5a12..0ef9e4d 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2212,6 +2212,8 @@
       "webui/chromeos/edu_coexistence_login_handler_chromeos.h",
       "webui/chromeos/edu_coexistence_state_tracker.cc",
       "webui/chromeos/edu_coexistence_state_tracker.h",
+      "webui/chromeos/emoji/emoji_picker.cc",
+      "webui/chromeos/emoji/emoji_picker.h",
       "webui/chromeos/image_source.cc",
       "webui/chromeos/image_source.h",
       "webui/chromeos/in_session_password_change/confirm_password_change_handler.cc",
@@ -4363,6 +4365,7 @@
       "//chrome/browser/apps/platform_apps",  # TODO(loyso): Remove this dep.
       "//chrome/browser/apps/platform_apps/api",
       "//chrome/browser/extensions",
+      "//chrome/browser/web_share_target",
       "//chrome/common/extensions/api",
       "//components/drive",
       "//components/guest_view/browser",
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index 9d8444a6..231215e 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -214,10 +214,6 @@
 
   DCHECK(model_);
 
-  if (chrome::SettingsWindowManager::UseDeprecatedSettingsWindow(profile)) {
-    settings_window_observer_ = std::make_unique<SettingsWindowObserver>();
-  }
-
   if (!profile) {
     // If no profile was passed, we take the currently active profile and use it
     // as the owner of the current desktop.
@@ -229,6 +225,10 @@
       profile = profile->GetOriginalProfile();
   }
 
+  if (chrome::SettingsWindowManager::UseDeprecatedSettingsWindow(profile)) {
+    settings_window_observer_ = std::make_unique<SettingsWindowObserver>();
+  }
+
   // All profile relevant settings get bound to the current profile.
   AttachProfile(profile);
   DCHECK_EQ(profile, profile_);
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index 6f46f6b..3b3752ee 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -538,7 +538,10 @@
     // preserve. Fallback to the behavior used for singletons: overwrite the
     // current tab if it's the NTP, otherwise open a new tab.
     params->disposition = WindowOpenDisposition::SINGLETON_TAB;
-    ShowSingletonTabOverwritingNTP(params->browser, std::move(*params));
+    // Copy to a local variable first to avoid std::move from clearing
+    // |params->browser|.
+    Browser* browser = params->browser;
+    ShowSingletonTabOverwritingNTP(browser, std::move(*params));
     return;
   }
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
index ab0b285..3513a4f 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
@@ -359,10 +359,9 @@
  public:
   GuestToolbarViewWithExtensionsToolbarMenuTest() : is_ephemeral_(GetParam()) {
     // Update for platforms which don't support ephemeral Guest profiles.
-    if (is_ephemeral_) {
-      is_ephemeral_ =
-          TestingProfile::SetScopedFeatureListForEphemeralGuestProfiles(
-              scoped_feature_list_, is_ephemeral_);
+    if (!TestingProfile::SetScopedFeatureListForEphemeralGuestProfiles(
+            scoped_feature_list_, is_ephemeral_)) {
+      is_ephemeral_ = false;
     }
   }
 
diff --git a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
index 61ac40f..29c3e42a 100644
--- a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/installable/installable_metrics.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -34,6 +35,7 @@
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/browser/web_applications/components/web_app_tab_helper_base.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/test/service_worker_registration_waiter.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/security_interstitials/content/security_interstitial_tab_helper.h"
@@ -113,7 +115,10 @@
 }
 
 AppId InstallWebAppFromManifest(Browser* browser, const GURL& app_url) {
+  ServiceWorkerRegistrationWaiter registration_waiter(browser->profile(),
+                                                      app_url);
   NavigateToURLAndWait(browser, app_url);
+  registration_waiter.AwaitRegistration();
 
   AppId app_id;
   base::RunLoop run_loop;
@@ -123,7 +128,7 @@
   WaitUntilReady(provider);
   provider->install_manager().InstallWebAppFromManifestWithFallback(
       browser->tab_strip_model()->GetActiveWebContents(),
-      /*force_shortcut_app=*/true, WebappInstallSource::MENU_BROWSER_TAB,
+      /*force_shortcut_app=*/false, WebappInstallSource::MENU_BROWSER_TAB,
       base::BindLambdaForTesting(
           [](content::WebContents* initiator_web_contents,
              std::unique_ptr<WebApplicationInfo> web_app_info,
diff --git a/chrome/browser/ui/web_applications/web_app_launch_manager.cc b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
index a74b79e..78e5311 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_manager.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_manager.cc
@@ -6,11 +6,17 @@
 
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/command_line.h"
 #include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/ranges/algorithm.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
@@ -37,6 +43,7 @@
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/browser/web_launch/web_launch_files_helper.h"
+#include "chrome/browser/web_share_target/target_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "content/public/browser/page_navigator.h"
@@ -44,12 +51,20 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/referrer.h"
 #include "extensions/common/constants.h"
+#include "net/base/mime_util.h"
+#include "services/network/public/cpp/resource_request_body.h"
+#include "storage/browser/file_system/file_system_context.h"
+#include "storage/browser/file_system/file_system_url.h"
 #include "third_party/blink/public/common/features.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/display/scoped_display_for_new_windows.h"
 #include "url/gurl.h"
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
+#endif
+
 namespace web_app {
 
 namespace {
@@ -71,6 +86,106 @@
   tab_helper->SetAppId(app_id);
 }
 
+content::WebContents* NavigateWebAppUsingParams(const std::string& app_id,
+                                                NavigateParams& nav_params) {
+  Navigate(&nav_params);
+
+  content::WebContents* const web_contents =
+      nav_params.navigated_or_inserted_contents;
+
+  SetTabHelperAppId(web_contents, app_id);
+  web_app::SetAppPrefsForWebContents(web_contents);
+
+  return web_contents;
+}
+
+NavigateParams NavigateParamsForShareTarget(
+    Browser* browser,
+    const apps::ShareTarget& share_target,
+    const apps::mojom::Intent& intent) {
+  NavigateParams nav_params(browser, share_target.action,
+                            ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
+
+#if defined(OS_CHROMEOS)
+  std::vector<std::string> names;
+  std::vector<std::string> values;
+  std::vector<std::string> filenames;
+  std::vector<std::string> types;
+  std::vector<bool> is_value_file_uris;
+
+  DCHECK(intent.mime_type.has_value());
+  DCHECK(intent.file_urls.has_value());
+
+  const std::string& mime_type = intent.mime_type.value();
+  std::string name;
+  for (const apps::ShareTarget::Files& files : share_target.params.files) {
+    // Filter on MIME types. Chrome OS does not filter on file extensions.
+    // https://w3c.github.io/web-share-target/level-2/#dfn-accepted
+    if (base::ranges::any_of(files.accept, [&mime_type](const auto& criteria) {
+          return !base::StartsWith(criteria, ".") &&
+                 net::MatchesMimeType(criteria, mime_type);
+        })) {
+      name = files.name;
+      break;
+    }
+  }
+  if (name.empty()) {
+    VLOG(1) << "Received unexpected MIME type: " << mime_type;
+  } else {
+    // Files for Web Share intents are created by the browser in
+    // a .WebShare directory, with generated file names and file urls - see
+    // //chrome/browser/webshare/chromeos/sharesheet_client.cc
+    for (const GURL& file_url : intent.file_urls.value()) {
+      storage::FileSystemContext* file_system_context =
+          file_manager::util::GetFileSystemContextForExtensionId(
+              browser->profile(), extension_misc::kFilesManagerAppId);
+      storage::FileSystemURL file_system_url =
+          file_system_context->CrackURL(file_url);
+      if (!file_system_url.is_valid()) {
+        VLOG(1) << "Received unexpected file URL: " << file_url.spec();
+        continue;
+      }
+
+      names.push_back(name);
+      values.push_back(file_system_url.path().AsUTF8Unsafe());
+      filenames.push_back(file_system_url.path().BaseName().AsUTF8Unsafe());
+      types.push_back(mime_type);
+      is_value_file_uris.push_back(true);
+    }
+  }
+
+  const std::string boundary = net::GenerateMimeMultipartBoundary();
+  const std::string header_list = base::StringPrintf(
+      "Content-Type: multipart/form-data; boundary=%s\r\n", boundary.c_str());
+  scoped_refptr<network::ResourceRequestBody> post_data =
+      web_share_target::ComputeMultipartBody(names, values, is_value_file_uris,
+                                             filenames, types, boundary);
+
+  nav_params.post_data = post_data;
+  nav_params.extra_headers = header_list;
+#else
+  // TODO(crbug.com/1153194): Support Web Share Target on Windows.
+  // TODO(crbug.com/1153195): Support Web Share Target on Mac.
+  NOTIMPLEMENTED();
+#endif
+
+  return nav_params;
+}
+
+GURL GetLaunchUrl(WebAppProvider& provider,
+                  const apps::AppLaunchParams& params,
+                  const apps::ShareTarget* share_target) {
+  if (!params.override_url.is_empty())
+    return params.override_url;
+
+  const GURL app_url =
+      share_target ? share_target->action
+                   : provider.registrar().GetAppLaunchUrl(params.app_id);
+  return provider.os_integration_manager()
+      .GetMatchingFileHandlerURL(params.app_id, params.launch_files)
+      .value_or(app_url);
+}
+
 }  // namespace
 
 Browser* CreateWebApplicationWindow(Profile* profile,
@@ -99,15 +214,7 @@
     WindowOpenDisposition disposition) {
   NavigateParams nav_params(browser, url, ui::PAGE_TRANSITION_AUTO_BOOKMARK);
   nav_params.disposition = disposition;
-  Navigate(&nav_params);
-
-  content::WebContents* const web_contents =
-      nav_params.navigated_or_inserted_contents;
-
-  SetTabHelperAppId(web_contents, app_id);
-  web_app::SetAppPrefsForWebContents(web_contents);
-
-  return web_contents;
+  return NavigateWebAppUsingParams(app_id, nav_params);
 }
 
 WebAppLaunchManager::WebAppLaunchManager(Profile* profile)
@@ -126,15 +233,13 @@
   if (GetOpenApplicationCallback())
     return GetOpenApplicationCallback().Run(std::move(params));
 
-  web_app::OsIntegrationManager& os_integration_manager =
-      provider_->os_integration_manager();
-
-  const GURL url =
-      params.override_url.is_empty()
-          ? os_integration_manager
-                .GetMatchingFileHandlerURL(params.app_id, params.launch_files)
-                .value_or(provider_->registrar().GetAppLaunchUrl(params.app_id))
-          : params.override_url;
+  // TODO(crbug.com/1125880): Support Web Share Target with no files shared.
+  const apps::ShareTarget* const share_target =
+      (params.intent && params.intent->mime_type.has_value() &&
+       params.intent->file_urls.has_value())
+          ? provider_->registrar().GetAppShareTarget(params.app_id)
+          : nullptr;
+  const GURL url = GetLaunchUrl(*provider_, params, share_target);
 
   // Place new windows on the specified display.
   display::ScopedDisplayForNewWindows scoped_display(params.display_id);
@@ -181,7 +286,12 @@
   }
 
   content::WebContents* web_contents;
-  if (disposition == WindowOpenDisposition::CURRENT_TAB) {
+  if (share_target) {
+    NavigateParams nav_params =
+        NavigateParamsForShareTarget(browser, *share_target, *params.intent);
+    nav_params.disposition = disposition;
+    web_contents = NavigateWebAppUsingParams(params.app_id, nav_params);
+  } else if (disposition == WindowOpenDisposition::CURRENT_TAB) {
     TabStripModel* const model = browser->tab_strip_model();
     content::WebContents* existing_tab = model->GetActiveWebContents();
     const int tab_index = model->GetIndexOfWebContents(existing_tab);
@@ -204,6 +314,8 @@
         browser, params.app_id, url, WindowOpenDisposition::NEW_FOREGROUND_TAB);
   }
 
+  web_app::OsIntegrationManager& os_integration_manager =
+      provider_->os_integration_manager();
   if (os_integration_manager.IsFileHandlingAPIAvailable(params.app_id)) {
     web_launch::WebLaunchFilesHelper::SetLaunchPaths(web_contents, url,
                                                      params.launch_files);
diff --git a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
new file mode 100644
index 0000000..9bd65cb
--- /dev/null
+++ b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
@@ -0,0 +1,181 @@
+// 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 <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/browser_app_launcher.h"
+#include "chrome/browser/apps/app_service/intent_util.h"
+#include "chrome/browser/apps/app_service/launch_utils.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/display/types/display_constants.h"
+#include "url/gurl.h"
+
+namespace {
+
+apps::AppServiceProxy* GetAppServiceProxy(Profile* profile) {
+  DCHECK(
+      apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile));
+  return apps::AppServiceProxyFactory::GetForProfile(profile);
+}
+
+base::FilePath PrepareWebShareDirectory(Profile* profile) {
+  constexpr base::FilePath::CharType kWebShareDirname[] =
+      FILE_PATH_LITERAL(".WebShare");
+  const base::FilePath directory =
+      file_manager::util::GetMyFilesFolderForProfile(profile).Append(
+          kWebShareDirname);
+
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::File::Error result = base::File::FILE_OK;
+  EXPECT_TRUE(base::CreateDirectoryAndGetError(directory, &result));
+  return directory;
+}
+
+void RemoveWebShareDirectory(const base::FilePath& directory) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  EXPECT_TRUE(base::DeletePathRecursively(directory));
+}
+
+base::FilePath StoreSharedFile(const base::FilePath& directory,
+                               const base::StringPiece& name,
+                               const base::StringPiece& content) {
+  const base::FilePath path = directory.Append(name);
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::File file(path,
+                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+  EXPECT_EQ(file.WriteAtCurrentPos(content.begin(), content.size()),
+            static_cast<int>(content.size()));
+  return path;
+}
+
+}  // namespace
+
+namespace web_app {
+
+class WebShareTargetBrowserTest : public WebAppControllerBrowserTest {
+ public:
+  GURL app_url() const {
+    return embedded_test_server()->GetURL("/web_share_target/charts.html");
+  }
+  GURL share_target_url() const {
+    return embedded_test_server()->GetURL("/web_share_target/share.html");
+  }
+
+  content::WebContents* LaunchAppWithIntent(const AppId& app_id,
+                                            apps::mojom::IntentPtr&& intent) {
+    apps::AppLaunchParams params = apps::CreateAppLaunchParamsForIntent(
+        app_id,
+        /*event_flags=*/0, apps::mojom::AppLaunchSource::kSourceAppLauncher,
+        display::kDefaultDisplayId,
+        apps::mojom::LaunchContainer::kLaunchContainerWindow,
+        std::move(intent));
+
+    ui_test_utils::UrlLoadObserver url_observer(
+        share_target_url(), content::NotificationService::AllSources());
+    content::WebContents* const web_contents =
+        GetAppServiceProxy(profile())
+            ->BrowserAppLauncher()
+            ->LaunchAppWithParams(std::move(params));
+    DCHECK(web_contents);
+    url_observer.Wait();
+    return web_contents;
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareText) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
+  const base::FilePath directory = PrepareWebShareDirectory(profile());
+
+  apps::mojom::IntentPtr intent;
+  {
+    const base::FilePath first_csv =
+        StoreSharedFile(directory, "first.csv", "1,2,3,4,5");
+    const base::FilePath second_csv =
+        StoreSharedFile(directory, "second.csv", "6,7,8,9,0");
+
+    std::vector<base::FilePath> file_paths({first_csv, second_csv});
+    std::vector<std::string> content_types(2, "text/csv");
+    intent = apps_util::CreateShareIntentFromFiles(
+        profile(), std::move(file_paths), std::move(content_types));
+  }
+
+  const std::string script = "document.getElementById('records').textContent";
+  content::WebContents* const web_contents =
+      LaunchAppWithIntent(app_id, std::move(intent));
+  const content::EvalJsResult result = content::EvalJs(web_contents, script);
+  EXPECT_EQ("1,2,3,4,5 6,7,8,9,0", result);
+
+  RemoveWebShareDirectory(directory);
+}
+
+IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareImage) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
+  const base::FilePath directory = PrepareWebShareDirectory(profile());
+
+  apps::mojom::IntentPtr intent;
+  {
+    const base::FilePath first_svg =
+        StoreSharedFile(directory, "first.svg", "picture");
+
+    std::vector<base::FilePath> file_paths({first_svg});
+    std::vector<std::string> content_types(1, "image/svg+xml");
+    intent = apps_util::CreateShareIntentFromFiles(
+        profile(), std::move(file_paths), std::move(content_types));
+  }
+
+  const std::string script = "document.getElementById('graphs').textContent";
+  content::WebContents* const web_contents =
+      LaunchAppWithIntent(app_id, std::move(intent));
+  const content::EvalJsResult result = content::EvalJs(web_contents, script);
+  EXPECT_EQ("picture", result);
+
+  RemoveWebShareDirectory(directory);
+}
+
+IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest, ShareAudio) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  const AppId app_id = web_app::InstallWebAppFromManifest(browser(), app_url());
+  const base::FilePath directory = PrepareWebShareDirectory(profile());
+
+  apps::mojom::IntentPtr intent;
+  {
+    const base::FilePath first_weba =
+        StoreSharedFile(directory, "first.weba", "a");
+    const base::FilePath second_weba =
+        StoreSharedFile(directory, "second.weba", "b");
+    const base::FilePath third_weba =
+        StoreSharedFile(directory, "third.weba", "c");
+
+    std::vector<base::FilePath> file_paths(
+        {first_weba, second_weba, third_weba});
+    std::vector<std::string> content_types(3, "audio/webm");
+    intent = apps_util::CreateShareIntentFromFiles(
+        profile(), std::move(file_paths), std::move(content_types));
+  }
+
+  const std::string script = "document.getElementById('notes').textContent";
+  content::WebContents* const web_contents =
+      LaunchAppWithIntent(app_id, std::move(intent));
+  const content::EvalJsResult result = content::EvalJs(web_contents, script);
+  EXPECT_EQ("a b c", result);
+
+  RemoveWebShareDirectory(directory);
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 7118b2d..354ed84f 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -186,6 +186,7 @@
 #include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_ui.h"
 #include "chrome/browser/ui/webui/chromeos/cryptohome_ui.h"
 #include "chrome/browser/ui/webui/chromeos/drive_internals_ui.h"
+#include "chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h"
 #include "chrome/browser/ui/webui/chromeos/internet_config_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
@@ -803,6 +804,9 @@
       return &NewWebUI<chromeos::ArcPowerControlUI>;
     }
   }
+  if (url.host_piece() == chrome::kChromeUIEmojiPickerHost) {
+    return &NewWebUI<chromeos::EmojiPicker>;
+  }
 
 #if !defined(OFFICIAL_BUILD)
 #if !defined(USE_REAL_DBUS_CLIENTS)
diff --git a/chrome/browser/ui/webui/chromeos/emoji/OWNERS b/chrome/browser/ui/webui/chromeos/emoji/OWNERS
new file mode 100644
index 0000000..2b7fe0fb
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/emoji/OWNERS
@@ -0,0 +1,4 @@
+jopalmer@chromium.org
+keithlee@chrome.org
+
+# COMPONENT: OS>Inputs
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.cc b/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.cc
new file mode 100644
index 0000000..b309016f
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.cc
@@ -0,0 +1,97 @@
+// Copyright 2020 The Chromium Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache v2.0 license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/browser_resources.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace chromeos {
+
+EmojiPicker::EmojiPicker(content::WebUI* web_ui)
+    : content::WebUIController(web_ui) {
+  // Set up the chrome://emoji-picker source.
+  content::WebUIDataSource* html_source =
+      content::WebUIDataSource::Create(chrome::kChromeUIEmojiPickerHost);
+
+  // As a demonstration of passing a variable for JS to use we pass in some
+  // emoji.
+  html_source->AddString("emoji",
+                         "😀,😃,😄,😁,😆,😅,😂,🤣,😭");
+  html_source->UseStringsJs();
+
+  // Add required resources.
+  html_source->AddResourcePath("emoji_picker.css", IDR_EMOJI_PICKER_CSS);
+  html_source->AddResourcePath("emoji_picker.js", IDR_EMOJI_PICKER_JS);
+  html_source->SetDefaultResource(IDR_EMOJI_PICKER_HTML);
+
+  content::BrowserContext* browser_context =
+      web_ui->GetWebContents()->GetBrowserContext();
+  content::WebUIDataSource::Add(browser_context, html_source);
+}
+
+EmojiPicker::~EmojiPicker() {}
+
+EmojiPickerDialog::EmojiPickerDialog() {}
+
+void EmojiPickerDialog::Show() {
+  chrome::ShowWebDialog(nullptr, ProfileManager::GetActiveUserProfile(),
+                        new EmojiPickerDialog());
+}
+
+ui::ModalType EmojiPickerDialog::GetDialogModalType() const {
+  return ui::MODAL_TYPE_NONE;
+}
+
+base::string16 EmojiPickerDialog::GetDialogTitle() const {
+  return base::UTF8ToUTF16("Emoji picker");
+}
+
+GURL EmojiPickerDialog::GetDialogContentURL() const {
+  return GURL(chrome::kChromeUIEmojiPickerURL);
+}
+
+void EmojiPickerDialog::GetWebUIMessageHandlers(
+    std::vector<content::WebUIMessageHandler*>* handlers) const {}
+
+void EmojiPickerDialog::GetDialogSize(gfx::Size* size) const {
+  const int kDefaultWidth = 544;
+  const int kDefaultHeight = 628;
+  size->SetSize(kDefaultWidth, kDefaultHeight);
+}
+
+std::string EmojiPickerDialog::GetDialogArgs() const {
+  return "";
+}
+
+void EmojiPickerDialog::OnDialogShown(content::WebUI* webui) {
+  webui_ = webui;
+}
+
+void EmojiPickerDialog::OnDialogClosed(const std::string& json_retval) {
+  delete this;
+}
+
+void EmojiPickerDialog::OnCloseContents(content::WebContents* source,
+                                        bool* out_close_dialog) {
+  *out_close_dialog = true;
+}
+
+bool EmojiPickerDialog::ShouldShowDialogTitle() const {
+  return true;
+}
+
+EmojiPickerDialog::~EmojiPickerDialog() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h b/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h
new file mode 100644
index 0000000..7f1eee8
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/emoji/emoji_picker.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Chromium Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache v2.0 license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_EMOJI_EMOJI_PICKER_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_EMOJI_EMOJI_PICKER_H_
+
+#include "base/macros.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "ui/web_dialogs/web_dialog_delegate.h"
+
+namespace chromeos {
+
+// The WebUI for chrome://emoji-picker
+class EmojiPicker : public content::WebUIController {
+ public:
+  explicit EmojiPicker(content::WebUI* web_ui);
+  ~EmojiPicker() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EmojiPicker);
+};
+
+class EmojiPickerDialog : public ui::WebDialogDelegate {
+ public:
+  static void Show();
+  ~EmojiPickerDialog() override;
+
+ private:
+  EmojiPickerDialog();
+  // ui::WebDialogDelegate:
+  ui::ModalType GetDialogModalType() const override;
+  base::string16 GetDialogTitle() const override;
+  GURL GetDialogContentURL() const override;
+  void GetWebUIMessageHandlers(
+      std::vector<content::WebUIMessageHandler*>* handlers) const override;
+  void GetDialogSize(gfx::Size* size) const override;
+  std::string GetDialogArgs() const override;
+  void OnDialogShown(content::WebUI* webui) override;
+  void OnDialogClosed(const std::string& json_retval) override;
+  void OnCloseContents(content::WebContents* source,
+                       bool* out_close_dialog) override;
+  bool ShouldShowDialogTitle() const override;
+
+  content::WebUI* webui_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(EmojiPickerDialog);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_EMOJI_EMOJI_PICKER_H_
diff --git a/chrome/browser/ui/webui/plural_string_handler.cc b/chrome/browser/ui/webui/plural_string_handler.cc
index d50043bd..3648fe0 100644
--- a/chrome/browser/ui/webui/plural_string_handler.cc
+++ b/chrome/browser/ui/webui/plural_string_handler.cc
@@ -7,8 +7,10 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/values.h"
+#include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_ui.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ui_strings.h"
 
 PluralStringHandler::PluralStringHandler() {}
 
@@ -19,6 +21,18 @@
       "getPluralString",
       base::BindRepeating(&PluralStringHandler::HandleGetPluralString,
                           base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "getPluralStringTupleWithComma",
+      base::BindRepeating(
+          &PluralStringHandler::HandleGetPluralStringTupleWithComma,
+          base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "getPluralStringTupleWithPeriods",
+      base::BindRepeating(
+          &PluralStringHandler::HandleGetPluralStringTupleWithPeriods,
+          base::Unretained(this)));
 }
 
 void PluralStringHandler::AddLocalizedString(const std::string& name, int id) {
@@ -37,10 +51,52 @@
   int count;
   CHECK(args->GetInteger(2, &count));
 
+  auto string = GetPluralizedStringForMessageName(message_name, count);
+
+  ResolveJavascriptCallback(*callback_id, base::Value(string));
+}
+
+void PluralStringHandler::HandleGetPluralStringTupleWithComma(
+    const base::ListValue* args) {
+  GetPluralStringTuple(args, IDS_CONCAT_TWO_STRINGS_WITH_COMMA);
+}
+
+void PluralStringHandler::HandleGetPluralStringTupleWithPeriods(
+    const base::ListValue* args) {
+  GetPluralStringTuple(args, IDS_CONCAT_TWO_STRINGS_WITH_PERIODS);
+}
+
+void PluralStringHandler::GetPluralStringTuple(const base::ListValue* args,
+                                               int string_tuple_id) {
+  AllowJavascript();
+  CHECK_EQ(5U, args->GetSize());
+  const base::Value* callback_id;
+  CHECK(args->Get(0, &callback_id));
+
+  std::string message_name1;
+  CHECK(args->GetString(1, &message_name1));
+
+  int count1;
+  CHECK(args->GetInteger(2, &count1));
+
+  std::string message_name2;
+  CHECK(args->GetString(3, &message_name2));
+
+  int count2;
+  CHECK(args->GetInteger(4, &count2));
+
+  auto string1 = GetPluralizedStringForMessageName(message_name1, count1);
+  auto string2 = GetPluralizedStringForMessageName(message_name2, count2);
+
+  ResolveJavascriptCallback(
+      *callback_id, base::Value(l10n_util::GetStringFUTF8(string_tuple_id,
+                                                          string1, string2)));
+}
+
+base::string16 PluralStringHandler::GetPluralizedStringForMessageName(
+    std::string message_name,
+    int count) {
   auto message_id_it = name_to_id_.find(message_name);
   CHECK(name_to_id_.end() != message_id_it);
-
-  ResolveJavascriptCallback(*callback_id,
-                            base::Value(l10n_util::GetPluralStringFUTF8(
-                                message_id_it->second, count)));
+  return l10n_util::GetPluralStringFUTF16(message_id_it->second, count);
 }
diff --git a/chrome/browser/ui/webui/plural_string_handler.h b/chrome/browser/ui/webui/plural_string_handler.h
index c953524..c123a51 100644
--- a/chrome/browser/ui/webui/plural_string_handler.h
+++ b/chrome/browser/ui/webui/plural_string_handler.h
@@ -22,6 +22,24 @@
  private:
   void HandleGetPluralString(const base::ListValue* args);
 
+  // Constructs two pluralized strings from the received arguments for the two
+  // strings, and then concatenates those with comma and whitespace in between.
+  void HandleGetPluralStringTupleWithComma(const base::ListValue* args);
+
+  // Constructs two pluralized strings from the received arguments for the two
+  // strings, and then concatenates those with period and whitespace in between,
+  // and a period afterwards.
+  void HandleGetPluralStringTupleWithPeriods(const base::ListValue* args);
+
+  // Constructs two pluralized strings from the received arguments for the two
+  // strings, and then concatenates those using the concatenation template
+  // specified. This method should only be called from within the
+  // |HandleGetPluralStringTuple*| methods above.
+  void GetPluralStringTuple(const base::ListValue* args, int string_tuple_id);
+
+  base::string16 GetPluralizedStringForMessageName(std::string message_name,
+                                                   int count);
+
   std::map<std::string, int> name_to_id_;
 
   DISALLOW_COPY_AND_ASSIGN(PluralStringHandler);
diff --git a/chrome/browser/ui/webui/predictors/predictors_handler.cc b/chrome/browser/ui/webui/predictors/predictors_handler.cc
index dea6997da3..066a0ee 100644
--- a/chrome/browser/ui/webui/predictors/predictors_handler.cc
+++ b/chrome/browser/ui/webui/predictors/predictors_handler.cc
@@ -46,6 +46,7 @@
 
 void PredictorsHandler::RequestAutocompleteActionPredictorDb(
     const base::ListValue* args) {
+  AllowJavascript();
   const bool enabled = !!autocomplete_action_predictor_;
   base::DictionaryValue dict;
   dict.SetBoolean("enabled", enabled);
@@ -67,12 +68,12 @@
     dict.Set("db", std::move(db));
   }
 
-  web_ui()->CallJavascriptFunctionUnsafe("updateAutocompleteActionPredictorDb",
-                                         dict);
+  ResolveJavascriptCallback(args->GetList()[0] /* callback_id */, dict);
 }
 
 void PredictorsHandler::RequestResourcePrefetchPredictorDb(
     const base::ListValue* args) {
+  AllowJavascript();
   const bool enabled = (loading_predictor_ != nullptr);
   base::DictionaryValue dict;
   dict.SetBoolean("enabled", enabled);
@@ -95,8 +96,7 @@
     }
   }
 
-  web_ui()->CallJavascriptFunctionUnsafe("updateResourcePrefetchPredictorDb",
-                                         dict);
+  ResolveJavascriptCallback(args->GetList()[0] /* callback_id */, dict);
 }
 
 void PredictorsHandler::AddOriginDataMapToListValue(
diff --git a/chrome/browser/ui/webui/predictors/predictors_ui.cc b/chrome/browser/ui/webui/predictors/predictors_ui.cc
index ba37462..cfc822d 100644
--- a/chrome/browser/ui/webui/predictors/predictors_ui.cc
+++ b/chrome/browser/ui/webui/predictors/predictors_ui.cc
@@ -19,7 +19,11 @@
 content::WebUIDataSource* CreatePredictorsUIHTMLSource() {
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIPredictorsHost);
+  source->AddResourcePath("autocomplete_action_predictor.js",
+                          IDR_PREDICTORS_AUTOCOMPLETE_ACTION_PREDICTOR_JS);
   source->AddResourcePath("predictors.js", IDR_PREDICTORS_JS);
+  source->AddResourcePath("resource_prefetch_predictor.js",
+                          IDR_PREDICTORS_RESOURCE_PREFETCH_PREDICTOR_JS);
   source->SetDefaultResource(IDR_PREDICTORS_HTML);
   return source;
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/main_section.cc b/chrome/browser/ui/webui/settings/chromeos/main_section.cc
index c704bfc..0897c77d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/main_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/main_section.cc
@@ -79,7 +79,7 @@
       // deadline.
       int days_remaining = days.value() ? days.value() : 1;
       base::string16 domain_name =
-          base::UTF8ToUTF16(connector->GetEnterpriseDisplayDomain());
+          base::UTF8ToUTF16(connector->GetEnterpriseDomainManager());
       base::string16 link_url =
           base::UTF8ToUTF16(chrome::kChromeUIManagementURL);
       if (days_remaining == 7) {
@@ -92,7 +92,7 @@
                 l10n_util::GetStringUTF16(
                     IDS_SETTINGS_UPDATE_REQUIRED_EOL_BANNER_DAYS),
                 days_remaining,
-                base::UTF8ToUTF16(connector->GetEnterpriseDisplayDomain()),
+                base::UTF8ToUTF16(connector->GetEnterpriseDomainManager()),
                 ui::GetChromeOSDeviceName(),
                 base::UTF8ToUTF16(chrome::kChromeUIManagementURL));
       }
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
index d25f0a7..158e13f 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
@@ -379,9 +379,12 @@
   DCHECK(features::IsPhoneHubEnabled());
   DCHECK(!notification_access_operation_);
 
-  if (notification_access_manager_->HasAccessBeenGranted()) {
-    PA_LOG(WARNING) << "Phonehub notification access has already been granted, "
-                       "returning early.";
+  phonehub::NotificationAccessManager::AccessStatus access_status =
+      notification_access_manager_->GetAccessStatus();
+  if (access_status != phonehub::NotificationAccessManager::AccessStatus::
+                           kAvailableButNotGranted) {
+    PA_LOG(WARNING) << "Cannot request notification access setup flow; current "
+                    << "status: " << access_status;
     return;
   }
 
@@ -473,16 +476,12 @@
           ? android_sms_pairing_state_tracker_->IsAndroidSmsPairingComplete()
           : false);
 
-  // TODO(khorimoto): Send prohibited value if notification access is
-  // prohibited.
-  static const int kAccessNotGranted = 1;
-  static const int kAccessGranted = 2;
-  int access_value = kAccessNotGranted;
-  if (notification_access_manager_ &&
-      notification_access_manager_->HasAccessBeenGranted()) {
-    access_value = kAccessGranted;
-  }
-  page_content_dictionary->SetInteger(kNotificationAccessStatus, access_value);
+  phonehub::NotificationAccessManager::AccessStatus access_status = phonehub::
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted;
+  if (notification_access_manager_)
+    access_status = notification_access_manager_->GetAccessStatus();
+  page_content_dictionary->SetInteger(kNotificationAccessStatus,
+                                      static_cast<int32_t>(access_status));
 
   return page_content_dictionary;
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
index a0559c2..fa14f1a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
@@ -158,7 +158,8 @@
         std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
     fake_notification_access_manager_ =
         std::make_unique<phonehub::FakeNotificationAccessManager>(
-            /*has_access_been_granted=*/false);
+            phonehub::NotificationAccessManager::AccessStatus::
+                kAvailableButNotGranted);
     fake_android_sms_pairing_state_tracker_ = std::make_unique<
         multidevice_setup::FakeAndroidSmsPairingStateTracker>();
     fake_android_sms_app_manager_ =
@@ -227,8 +228,11 @@
   }
 
   void CallAttemptNotificationSetup(bool has_access_been_granted) {
-    fake_notification_access_manager()->SetHasAccessBeenGrantedInternal(
-        has_access_been_granted);
+    fake_notification_access_manager()->SetAccessStatusInternal(
+        has_access_been_granted
+            ? phonehub::NotificationAccessManager::AccessStatus::kAccessGranted
+            : phonehub::NotificationAccessManager::AccessStatus::
+                  kAvailableButNotGranted);
     base::ListValue empty_args;
     test_web_ui()->HandleReceivedMessage("attemptNotificationSetup",
                                          &empty_args);
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.cc b/chrome/browser/ui/webui/settings/safety_check_handler.cc
index be940e1..00b27c9 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.cc
@@ -35,6 +35,7 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension_id.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ui_strings.h"
 
 #if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #include "base/win/registry.h"
@@ -672,7 +673,7 @@
       } else {
         // Both compromised and weak passwords.
         return l10n_util::GetStringFUTF16(
-            IDS_SETTINGS_SAFETY_CHECK_STRING_TUPLE_WITH_COMMA,
+            IDS_CONCAT_TWO_STRINGS_WITH_COMMA,
             l10n_util::GetPluralStringFUTF16(
                 IDS_SETTINGS_SAFETY_CHECK_COMPROMISED_PASSWORDS,
                 compromised.value()),
@@ -727,16 +728,14 @@
           IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
           reenabled_user.value());
     case ExtensionsStatus::kBlocklistedReenabledSomeByUser:
-      // TODO(crbug/1060625): Make string concatenation with a period
-      // internationalized (see go/i18n-concatenation).
-      return l10n_util::GetPluralStringFUTF16(
-                 IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
-                 reenabled_user.value()) +
-             base::ASCIIToUTF16(". ") +
-             l10n_util::GetPluralStringFUTF16(
-                 IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
-                 reenabled_admin.value()) +
-             base::ASCIIToUTF16(".");
+      return l10n_util::GetStringFUTF16(
+          IDS_CONCAT_TWO_STRINGS_WITH_PERIODS,
+          l10n_util::GetPluralStringFUTF16(
+              IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
+              reenabled_user.value()),
+          l10n_util::GetPluralStringFUTF16(
+              IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
+              reenabled_admin.value()));
     case ExtensionsStatus::kBlocklistedReenabledAllByAdmin:
       return l10n_util::GetPluralStringFUTF16(
           IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
diff --git a/chrome/browser/webshare/chromeos/sharesheet_client.cc b/chrome/browser/webshare/chromeos/sharesheet_client.cc
index ae646b9..dc41bc4 100644
--- a/chrome/browser/webshare/chromeos/sharesheet_client.cc
+++ b/chrome/browser/webshare/chromeos/sharesheet_client.cc
@@ -91,12 +91,6 @@
     return;
   }
 
-  if (files.empty()) {
-    // TODO(crbug.com/1127670): Support title/text/url sharing without files.
-    std::move(callback).Run(blink::mojom::ShareError::CANCELED);
-    return;
-  }
-
   Profile* const profile =
       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
   DCHECK(profile);
@@ -117,6 +111,16 @@
   current_share_->title = title;
   current_share_->callback = std::move(callback);
 
+  if (current_share_->files.empty()) {
+    GetSharesheetCallback().Run(
+        web_contents(), current_share_->file_paths,
+        current_share_->content_types, current_share_->text,
+        current_share_->title,
+        base::BindOnce(&SharesheetClient::OnShowSharesheet,
+                       weak_ptr_factory_.GetWeakPtr()));
+    return;
+  }
+
   current_share_->prepare_directory_task =
       std::make_unique<PrepareDirectoryTask>(
           current_share_->directory, kMaxSharedFileBytes,
@@ -167,9 +171,8 @@
   }
 
   GetSharesheetCallback().Run(
-      web_contents(), std::move(current_share_->file_paths),
-      std::move(current_share_->content_types), current_share_->text,
-      current_share_->title,
+      web_contents(), current_share_->file_paths, current_share_->content_types,
+      current_share_->text, current_share_->title,
       base::BindOnce(&SharesheetClient::OnShowSharesheet,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/webshare/chromeos/sharesheet_client_browsertest.cc b/chrome/browser/webshare/chromeos/sharesheet_client_browsertest.cc
index 2a073637..a566235e 100644
--- a/chrome/browser/webshare/chromeos/sharesheet_client_browsertest.cc
+++ b/chrome/browser/webshare/chromeos/sharesheet_client_browsertest.cc
@@ -59,12 +59,14 @@
     return embedded_test_server()->GetURL("/webshare/index.html");
   }
 
-  void ConfirmShareText(const std::string& script,
-                        const char* expected_text,
-                        const char* expected_title) {
+  void ConfirmShareText(
+      const std::string& script,
+      const char* expected_text,
+      const char* expected_title,
+      const std::vector<std::string>& expected_content_types) {
     SharesheetClient::SetSharesheetCallbackForTesting(
         base::BindLambdaForTesting(
-            [&expected_text, &expected_title](
+            [&expected_text, &expected_title, &expected_content_types](
                 content::WebContents* in_contents,
                 const std::vector<base::FilePath>& file_paths,
                 const std::vector<std::string>& content_types,
@@ -72,14 +74,15 @@
                 SharesheetClient::CloseCallback close_callback) {
               EXPECT_EQ(text, expected_text);
               EXPECT_EQ(title, expected_title);
+              EXPECT_EQ(file_paths.size(), content_types.size());
+              EXPECT_EQ(content_types, expected_content_types);
               std::move(close_callback)
-                  .Run(sharesheet::SharesheetResult::kCancel);
+                  .Run(sharesheet::SharesheetResult::kSuccess);
             }));
 
     content::WebContents* const contents =
         browser()->tab_strip_model()->GetActiveWebContents();
-    EXPECT_EQ("share failed: AbortError: Share canceled",
-              content::EvalJs(contents, script));
+    EXPECT_EQ("share succeeded", content::EvalJs(contents, script));
   }
 
  private:
@@ -179,24 +182,45 @@
             content::EvalJs(contents, script));
 }
 
+IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, Text) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ui_test_utils::NavigateToURL(browser(), GetAppUrl());
+  ConfirmShareText("share_title()",
+                   /*expected_text=*/"",
+                   /*expected_title=*/"Subject", /*expected_content_types=*/{});
+  ConfirmShareText("share_title_url()",
+                   /*expected_text=*/"https://example.com/",
+                   /*expected_title=*/"Subject", /*expected_content_types=*/{});
+  ConfirmShareText("share_text()",
+                   /*expected_text=*/"Message",
+                   /*expected_title=*/"", /*expected_content_types=*/{});
+  ConfirmShareText("share_text_url()",
+                   /*expected_text=*/"Message https://example.com/",
+                   /*expected_title=*/"", /*expected_content_types=*/{});
+  ConfirmShareText("share_url()",
+                   /*expected_text=*/"https://example.com/",
+                   /*expected_title=*/"", /*expected_content_types=*/{});
+}
+
 IN_PROC_BROWSER_TEST_F(SharesheetClientBrowserTest, TextWithFile) {
   ASSERT_TRUE(embedded_test_server()->Start());
   ui_test_utils::NavigateToURL(browser(), GetAppUrl());
+  const std::vector<std::string> expected_content_types{"image/webp"};
   ConfirmShareText("share_file_title()",
                    /*expected_text=*/"",
-                   /*expected_title=*/"Subject");
+                   /*expected_title=*/"Subject", expected_content_types);
   ConfirmShareText("share_file_title_url()",
                    /*expected_text=*/"https://example.com/",
-                   /*expected_title=*/"Subject");
+                   /*expected_title=*/"Subject", expected_content_types);
   ConfirmShareText("share_file_text()",
                    /*expected_text=*/"Message",
-                   /*expected_title=*/"");
+                   /*expected_title=*/"", expected_content_types);
   ConfirmShareText("share_file_text_url()",
                    /*expected_text=*/"Message https://example.com/",
-                   /*expected_title=*/"");
+                   /*expected_title=*/"", expected_content_types);
   ConfirmShareText("share_file_url()",
                    /*expected_text=*/"https://example.com/",
-                   /*expected_title=*/"");
+                   /*expected_title=*/"", expected_content_types);
 }
 
 }  // namespace webshare
diff --git a/chrome/browser/webshare/chromeos/store_file_task.cc b/chrome/browser/webshare/chromeos/store_file_task.cc
index 88aed59..5e5aec2 100644
--- a/chrome/browser/webshare/chromeos/store_file_task.cc
+++ b/chrome/browser/webshare/chromeos/store_file_task.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/webshare/chromeos/store_file_task.h"
 
+#include "base/strings/string_util.h"
 #include "base/threading/scoped_blocking_call.h"
 
 namespace webshare {
@@ -17,7 +18,11 @@
       available_space_(available_space),
       callback_(std::move(callback)),
       read_pipe_watcher_(FROM_HERE,
-                         mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {}
+                         mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
+  // |filename| is generated by GenerateFileName().
+  DCHECK(!filename_.ReferencesParent());
+  DCHECK(base::StartsWith(filename_.BaseName().MaybeAsASCII(), "share"));
+}
 
 StoreFileTask::~StoreFileTask() = default;
 
diff --git a/chrome/browser/webshare/share_service_browsertest.cc b/chrome/browser/webshare/share_service_browsertest.cc
index 5d41efa6..0aeffc4 100644
--- a/chrome/browser/webshare/share_service_browsertest.cc
+++ b/chrome/browser/webshare/share_service_browsertest.cc
@@ -13,6 +13,10 @@
 #include "content/public/test/browser_test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/webshare/chromeos/sharesheet_client.h"
+#endif
 #if defined(OS_WIN)
 #include "chrome/browser/webshare/win/scoped_share_operation_fake_components.h"
 #endif
@@ -23,13 +27,28 @@
     feature_list_.InitAndEnableFeature(features::kWebShare);
   }
 
-#if defined(OS_WIN)
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
+#if defined(OS_CHROMEOS)
+    webshare::SharesheetClient::SetSharesheetCallbackForTesting(
+        base::BindRepeating(&ShareServiceBrowserTest::AcceptShareRequest));
+#endif
+#if defined(OS_WIN)
     if (!IsSupportedEnvironment())
       return;
 
     ASSERT_NO_FATAL_FAILURE(scoped_fake_components_.SetUp());
+#endif
+  }
+
+#if defined(OS_CHROMEOS)
+  static void AcceptShareRequest(content::WebContents* web_contents,
+                                 const std::vector<base::FilePath>& file_paths,
+                                 const std::vector<std::string>& content_types,
+                                 const std::string& text,
+                                 const std::string& title,
+                                 sharesheet::CloseCallback close_callback) {
+    std::move(close_callback).Run(sharesheet::SharesheetResult::kSuccess);
   }
 #endif
 
@@ -63,10 +82,5 @@
   const std::string script = "share_text('hello')";
   const content::EvalJsResult result = content::EvalJs(contents, script);
 
-#if defined(OS_CHROMEOS)
-  // ChromeOS currently only supports file sharing.
-  EXPECT_EQ("share failed: AbortError: Share canceled", result);
-#else
   EXPECT_EQ("share succeeded", result);
-#endif
 }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 0898293..74c17b0 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1607252866-c2fa6eaa51da4882fc07c7786de461457a327487.profdata
+chrome-linux-master-1607320769-89cdddd8995ad3be12a676e570aae350fc15988d.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index b1ee5a0..180519c 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1607252866-f3ab87149644d613d134273463ed0ee5ad40f575.profdata
+chrome-mac-master-1607320769-d0ad4f5a17a630b334cdaa2bc2220f2b7c7c5cc2.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 192b61e9..95935400 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1607252866-d41b621d05e6450c8f7df54cb40144efff641518.profdata
+chrome-win32-master-1607320769-266f8d6f8ffba3874a821f44f98b77515c76c883.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 40525035..226cabf2 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1607241930-f5b83cf2f1c9491df564851856be44747b1ca52b.profdata
+chrome-win64-master-1607309475-be47e6675d78a40c2743c701a82c248ac3d955c3.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index bf75ccc..e90c9f83 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -243,6 +243,11 @@
 const base::Feature kDesktopPWAsCacheDuringDefaultInstall{
     "DesktopPWAsCacheDuringDefaultInstall", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Moves the Extensions "puzzle piece" icon from the title bar into the app menu
+// for web app windows.
+const base::Feature kDesktopPWAsElidedExtensionsMenu{
+    "DesktopPWAsElidedExtensionsMenu", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables local PWA installs to update their app manifest data if the site
 // changes its manifest.
 const base::Feature kDesktopPWAsLocalUpdating{"DesktopPWAsLocalUpdating",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 9e44bda2..001b973d 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -177,6 +177,9 @@
 extern const base::Feature kDesktopPWAsCacheDuringDefaultInstall;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsElidedExtensionsMenu;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDesktopPWAsLocalUpdating;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 12dfe7b..5ad9a18 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -292,6 +292,9 @@
 const char kChromeUIUntrustedTerminalURL[] = "chrome-untrusted://terminal/";
 const char kChromeUIUserImageHost[] = "userimage";
 const char kChromeUIUserImageURL[] = "chrome://userimage/";
+const char kChromeUIEmojiPickerURL[] = "chrome://emoji-picker/";
+const char kChromeUIEmojiPickerHost[] = "emoji-picker";
+
 const char kChromeUIUrgentPasswordExpiryNotificationHost[] =
     "urgent-password-expiry-notification";
 const char kChromeUIUrgentPasswordExpiryNotificationUrl[] =
@@ -329,6 +332,7 @@
       kChromeUISetTimeHost,
       kChromeUISmbCredentialsHost,
       kChromeUISmbShareHost,
+      kChromeUIEmojiPickerHost,
   };
   for (const char* h : kHosts) {
     if (host == h)
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 8dfef70b..5fba9fa34 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -240,6 +240,8 @@
 extern const char kChromeUICrostiniUpgraderUrl[];
 extern const char kChromeUICryptohomeHost[];
 extern const char kChromeUIDeviceEmulatorHost[];
+extern const char kChromeUIEmojiPickerURL[];
+extern const char kChromeUIEmojiPickerHost[];
 extern const char kChromeUIIntenetConfigDialogURL[];
 extern const char kChromeUIIntenetDetailDialogURL[];
 extern const char kChromeUIInternetConfigDialogHost[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4b7cbe6..a3c0970 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2818,6 +2818,7 @@
         "../browser/ui/views/supervised_user/parent_permission_dialog_view_browsertest.cc",
         "../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc",
         "../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc",
+        "../browser/ui/web_applications/web_share_target_browsertest.cc",
         "../browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder_browsertest.cc",
         "../browser/ui/webui/chromeos/add_supervision/add_supervision_ui_browsertest.cc",
         "../browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc",
diff --git a/chrome/test/data/web_share_target/charts.html b/chrome/test/data/web_share_target/charts.html
index 4bd0a23b..4d5fea6 100644
--- a/chrome/test/data/web_share_target/charts.html
+++ b/chrome/test/data/web_share_target/charts.html
@@ -1,13 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <meta charset="utf-8">
   <link rel="manifest" href="charts.json">
   <link rel="icon" href="basic-48.png">
 </head>
 <body>
   <h1>Charts web app</h1>
   <script>
-    navigator.serviceWorker.register('/web_share_target/service_worker.js');
+    window.onload = () => {
+      navigator.serviceWorker.register('/web_share_target/service_worker.js');
+    };
   </script>
 </body>
 </html>
diff --git a/chrome/test/data/web_share_target/charts.json b/chrome/test/data/web_share_target/charts.json
index 1425540c..3dba9252 100644
--- a/chrome/test/data/web_share_target/charts.json
+++ b/chrome/test/data/web_share_target/charts.json
@@ -23,11 +23,15 @@
       "files": [
         {
           "name": "records",
-          "accept": ["text/csv", ".csv"]
+          "accept": ["text/*", ".csv"]
         },
         {
           "name": "graphs",
           "accept": "image/svg+xml"
+        },
+        {
+          "name": "notes",
+          "accept": "*/*"
         }
       ]
     }
diff --git a/chrome/test/data/web_share_target/partial-wild.html b/chrome/test/data/web_share_target/partial-wild.html
new file mode 100644
index 0000000..4fcd635
--- /dev/null
+++ b/chrome/test/data/web_share_target/partial-wild.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <link rel="manifest" href="partial-wild.json">
+  <link rel="icon" href="basic-48.png">
+</head>
+<body>
+  <h1>Partial Wild</h1>
+  <script>
+    window.onload = () => {
+      navigator.serviceWorker.register('/web_share_target/service_worker.js');
+    };
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_share_target/partial-wild.json b/chrome/test/data/web_share_target/partial-wild.json
new file mode 100644
index 0000000..7eec5bba
--- /dev/null
+++ b/chrome/test/data/web_share_target/partial-wild.json
@@ -0,0 +1,31 @@
+{
+  "name": "Partial Wild",
+  "icons": [
+    {
+      "src": "basic-48.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "partial-wild.html",
+  "display": "minimal-ui",
+
+  "share_target": {
+    "action": "/web_share_target/share.html",
+    "method": "POST",
+    "enctype": "multipart/form-data",
+    "params": {
+      "files": [
+        {
+          "name": "graphs",
+          "accept": "image/*"
+        }
+      ]
+    }
+  }
+}
diff --git a/chrome/test/data/web_share_target/service_worker.js b/chrome/test/data/web_share_target/service_worker.js
index 9296744..534e201 100644
--- a/chrome/test/data/web_share_target/service_worker.js
+++ b/chrome/test/data/web_share_target/service_worker.js
@@ -4,12 +4,78 @@
 
 'use strict';
 
-self.addEventListener('fetch', e => {
-  e.respondWith((async () => {
-    try {
-      return await fetch(e.request);
-    } catch (error) {
-      return new Response('Hello offline page');
+// Promise-based version of FileReader.readAsText.
+function readAsFilePromise(fileReader, blob, encoding) {
+  return new Promise(resolve => {
+    fileReader.onload = e => resolve(e.target.result);
+    fileReader.readAsText(blob, encoding);
+  });
+}
+
+function respondToShare(event) {
+  event.respondWith((async () => {
+    const template = await fetch('share.template.html');
+    let body = await template.text();
+    const formData = await event.request.formData();
+
+    const init = {
+      status: 200,
+      statusText: 'OK',
+      headers: {'Content-Type': 'text/html'}
+    };
+
+    const file_fields = ['records', 'graphs', 'notes'];
+
+    let field_index = 0;
+    let files = undefined;
+    let file_contents = '';
+    let index = 0;
+
+    function prepareField() {
+      files = formData.getAll(
+          file_fields[field_index]);  // sequence of File objects
+      file_contents = '';
+      index = 0;
     }
+
+    prepareField();
+
+    async function progress() {
+      while (index === files.length) {
+        body = body.replace(
+            '{{' + file_fields[field_index] + '}}', file_contents);
+
+        ++field_index;
+        if (field_index === file_fields.length) {
+          return new Response(body, init);
+        }
+        prepareField();
+      }
+
+      const fileReader = new FileReader();
+      const dataFromFileLoaded =
+          await readAsFilePromise(fileReader, files[index], 'UTF-8');
+      if (index > 0) {
+        file_contents += ' ';
+      }
+      file_contents += dataFromFileLoaded;
+      index += 1;
+      return await progress();
+    }
+
+    return await progress();
   })());
+}
+
+self.addEventListener('activate', event => {
+  event.waitUntil(clients.claim());
+});
+
+self.addEventListener('fetch', event => {
+  const pathname = (new URL(event.request.url)).pathname;
+  if (pathname.endsWith('/share.html')) {
+    respondToShare(event);
+  } else {
+    event.respondWith(fetch(event.request));
+  }
 });
diff --git a/chrome/test/data/web_share_target/share.template.html b/chrome/test/data/web_share_target/share.template.html
new file mode 100644
index 0000000..1ca52c6
--- /dev/null
+++ b/chrome/test/data/web_share_target/share.template.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<head>
+  <meta charset="utf-8">
+  <link rel="icon" href="basic-48.png">
+</head>
+<body>
+  <section id="records">{{records}}</section>
+  <section id="graphs">{{graphs}}</section>
+  <section id="notes">{{notes}}</section>
+</body>
+</html>
diff --git a/chrome/test/data/webshare/index.html b/chrome/test/data/webshare/index.html
index 2ee7f23..5be015a 100644
--- a/chrome/test/data/webshare/index.html
+++ b/chrome/test/data/webshare/index.html
@@ -33,7 +33,7 @@
     async function share_multiple_files() {
       try {
         const first_file = create_file('sample.mp3', 'audio/mp3', 345);
-        const second_file = create_file('sample.mp4', 'video/mp4', 67890);
+        const second_file = create_file('../sample.mp4', 'video/mp4', 67890);
         await navigator.share({files: [first_file, second_file]});
         return 'share succeeded';
       } catch(error) {
@@ -41,6 +41,53 @@
       }
     }
 
+    async function share_title() {
+      try {
+        await navigator.share({title: "Subject"});
+        return 'share succeeded';
+      } catch(error) {
+        return ('share failed: ' + error);
+      }
+    }
+
+    async function share_title_url() {
+      try {
+        await navigator.share({title: "Subject",
+                               url: "https://example.com/"});
+        return 'share succeeded';
+      } catch(error) {
+        return ('share failed: ' + error);
+      }
+    }
+
+    async function share_text() {
+      try {
+        await navigator.share({text: "Message"});
+        return 'share succeeded';
+      } catch(error) {
+        return ('share failed: ' + error);
+      }
+    }
+
+    async function share_text_url() {
+      try {
+        await navigator.share({text: "Message",
+                               url: "https://example.com/"});
+        return 'share succeeded';
+      } catch(error) {
+        return ('share failed: ' + error);
+      }
+    }
+
+    async function share_url() {
+      try {
+        await navigator.share({url: "https://example.com/"});
+        return 'share succeeded';
+      } catch(error) {
+        return ('share failed: ' + error);
+      }
+    }
+
     async function share_file_title() {
       try {
         const single_file = create_file('sample.webp', 'image/webp', 12);
diff --git a/chrome/test/data/webui/test_plural_string_proxy.js b/chrome/test/data/webui/test_plural_string_proxy.js
index 5c61ffd1..b2607fd4 100644
--- a/chrome/test/data/webui/test_plural_string_proxy.js
+++ b/chrome/test/data/webui/test_plural_string_proxy.js
@@ -27,4 +27,22 @@
     this.methodCalled('getPluralString', {messageName, itemCount});
     return Promise.resolve(this.text);
   }
+
+  /** @override */
+  getPluralStringTupleWithComma(
+      messageName1, itemCount1, messageName2, itemCount2) {
+    this.methodCalled(
+        'getPluralStringTupleWithComma',
+        {messageName1, itemCount1, messageName2, itemCount2});
+    return Promise.resolve(this.text);
+  }
+
+  /** @override */
+  getPluralStringTupleWithPeriods(
+      messageName1, itemCount1, messageName2, itemCount2) {
+    this.methodCalled(
+        'getPluralStringTupleWithPeriods',
+        {messageName1, itemCount1, messageName2, itemCount2});
+    return Promise.resolve(this.text);
+  }
 }
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 9ece0e78..18c2b02 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13646.0.0
\ No newline at end of file
+13648.0.0
\ No newline at end of file
diff --git a/chromeos/components/phonehub/fake_notification_access_manager.cc b/chromeos/components/phonehub/fake_notification_access_manager.cc
index 8503773..ce9f789 100644
--- a/chromeos/components/phonehub/fake_notification_access_manager.cc
+++ b/chromeos/components/phonehub/fake_notification_access_manager.cc
@@ -8,22 +8,23 @@
 namespace phonehub {
 
 FakeNotificationAccessManager::FakeNotificationAccessManager(
-    bool has_access_been_granted)
-    : has_access_been_granted_(has_access_been_granted) {}
+    AccessStatus access_status)
+    : access_status_(access_status) {}
 
 FakeNotificationAccessManager::~FakeNotificationAccessManager() = default;
 
-void FakeNotificationAccessManager::SetHasAccessBeenGrantedInternal(
-    bool has_access_been_granted) {
-  if (has_access_been_granted_ == has_access_been_granted)
+void FakeNotificationAccessManager::SetAccessStatusInternal(
+    AccessStatus access_status) {
+  if (access_status_ == access_status)
     return;
 
-  has_access_been_granted_ = has_access_been_granted;
+  access_status_ = access_status;
   NotifyNotificationAccessChanged();
 }
 
-bool FakeNotificationAccessManager::HasAccessBeenGranted() const {
-  return has_access_been_granted_;
+NotificationAccessManager::AccessStatus
+FakeNotificationAccessManager::GetAccessStatus() const {
+  return access_status_;
 }
 
 bool FakeNotificationAccessManager::HasNotificationSetupUiBeenDismissed()
@@ -41,9 +42,17 @@
 
 void FakeNotificationAccessManager::SetNotificationSetupOperationStatus(
     NotificationAccessSetupOperation::Status new_status) {
-  if (new_status ==
-      NotificationAccessSetupOperation::Status::kCompletedSuccessfully) {
-    SetHasAccessBeenGrantedInternal(true);
+  switch (new_status) {
+    case NotificationAccessSetupOperation::Status::kCompletedSuccessfully:
+      SetAccessStatusInternal(AccessStatus::kAccessGranted);
+      break;
+    case NotificationAccessSetupOperation::Status::
+        kProhibitedFromProvidingAccess:
+      SetAccessStatusInternal(AccessStatus::kProhibited);
+      break;
+    default:
+      // Do not update access status based on other operation status values.
+      break;
   }
 
   NotificationAccessManager::SetNotificationSetupOperationStatus(new_status);
diff --git a/chromeos/components/phonehub/fake_notification_access_manager.h b/chromeos/components/phonehub/fake_notification_access_manager.h
index a0a2af3..17d8b4b 100644
--- a/chromeos/components/phonehub/fake_notification_access_manager.h
+++ b/chromeos/components/phonehub/fake_notification_access_manager.h
@@ -12,24 +12,25 @@
 
 class FakeNotificationAccessManager : public NotificationAccessManager {
  public:
-  explicit FakeNotificationAccessManager(bool has_access_been_granted = false);
+  explicit FakeNotificationAccessManager(
+      AccessStatus access_status = AccessStatus::kAvailableButNotGranted);
   ~FakeNotificationAccessManager() override;
 
   using NotificationAccessManager::IsSetupOperationInProgress;
 
-  void SetHasAccessBeenGrantedInternal(bool has_access_been_granted) override;
+  void SetAccessStatusInternal(AccessStatus access_status) override;
   void SetNotificationSetupOperationStatus(
       NotificationAccessSetupOperation::Status new_status);
 
   // NotificationAccessManager:
-  bool HasAccessBeenGranted() const override;
+  AccessStatus GetAccessStatus() const override;
   bool HasNotificationSetupUiBeenDismissed() const override;
   void DismissSetupRequiredUi() override;
 
   void ResetHasNotificationSetupUiBeenDismissed();
 
  private:
-  bool has_access_been_granted_;
+  AccessStatus access_status_;
   bool has_notification_setup_ui_been_dismissed_ = false;
 };
 
diff --git a/chromeos/components/phonehub/multidevice_setup_state_updater.cc b/chromeos/components/phonehub/multidevice_setup_state_updater.cc
index 00d958f..7d1574b 100644
--- a/chromeos/components/phonehub/multidevice_setup_state_updater.cc
+++ b/chromeos/components/phonehub/multidevice_setup_state_updater.cc
@@ -43,8 +43,10 @@
 }
 
 void MultideviceSetupStateUpdater::OnNotificationAccessChanged() {
-  if (notification_access_manager_->HasAccessBeenGranted())
+  if (notification_access_manager_->GetAccessStatus() ==
+      NotificationAccessManager::AccessStatus::kAccessGranted) {
     return;
+  }
 
   PA_LOG(INFO) << "Disabling PhoneHubNotifications feature.";
 
diff --git a/chromeos/components/phonehub/multidevice_setup_state_updater_unittest.cc b/chromeos/components/phonehub/multidevice_setup_state_updater_unittest.cc
index 3233fe5e..303bd96 100644
--- a/chromeos/components/phonehub/multidevice_setup_state_updater_unittest.cc
+++ b/chromeos/components/phonehub/multidevice_setup_state_updater_unittest.cc
@@ -36,7 +36,10 @@
   }
 
   void SetNotififcationAccess(bool enabled) {
-    fake_notification_access_manager_.SetHasAccessBeenGrantedInternal(enabled);
+    fake_notification_access_manager_.SetAccessStatusInternal(
+        enabled
+            ? NotificationAccessManager::AccessStatus::kAccessGranted
+            : NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
   }
 
   void SetFeatureState(Feature feature, FeatureState feature_state) {
diff --git a/chromeos/components/phonehub/notification_access_manager.cc b/chromeos/components/phonehub/notification_access_manager.cc
index 2cc7053b..928f5c59 100644
--- a/chromeos/components/phonehub/notification_access_manager.cc
+++ b/chromeos/components/phonehub/notification_access_manager.cc
@@ -18,7 +18,9 @@
 std::unique_ptr<NotificationAccessSetupOperation>
 NotificationAccessManager::AttemptNotificationSetup(
     NotificationAccessSetupOperation::Delegate* delegate) {
-  if (HasAccessBeenGranted())
+  // Should only be able to start the setup process if notification access is
+  // available but not yet granted.
+  if (GetAccessStatus() != AccessStatus::kAvailableButNotGranted)
     return nullptr;
 
   int operation_id = next_operation_id_;
@@ -75,5 +77,21 @@
     PA_LOG(INFO) << "Notification access setup operation has ended.";
 }
 
+std::ostream& operator<<(std::ostream& stream,
+                         NotificationAccessManager::AccessStatus status) {
+  switch (status) {
+    case NotificationAccessManager::AccessStatus::kProhibited:
+      stream << "[Access prohibited]";
+      break;
+    case NotificationAccessManager::AccessStatus::kAvailableButNotGranted:
+      stream << "[Access available but not granted]";
+      break;
+    case NotificationAccessManager::AccessStatus::kAccessGranted:
+      stream << "[Access granted]";
+      break;
+  }
+  return stream;
+}
+
 }  // namespace phonehub
 }  // namespace chromeos
diff --git a/chromeos/components/phonehub/notification_access_manager.h b/chromeos/components/phonehub/notification_access_manager.h
index 14ee5f7..87a329df 100644
--- a/chromeos/components/phonehub/notification_access_manager.h
+++ b/chromeos/components/phonehub/notification_access_manager.h
@@ -5,6 +5,8 @@
 #ifndef CHROMEOS_COMPONENTS_PHONEHUB_NOTIFICATION_ACCESS_MANAGER_H_
 #define CHROMEOS_COMPONENTS_PHONEHUB_NOTIFICATION_ACCESS_MANAGER_H_
 
+#include <ostream>
+
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -25,11 +27,25 @@
 // access setup flow via AttemptNotificationSetup().
 class NotificationAccessManager {
  public:
+  // Status of notification access. Numerical values are stored in prefs and
+  // should not be changed or reused.
+  enum class AccessStatus {
+    // Access has not been granted and is prohibited from being granted (e.g.,
+    // if the phone is using a Work Profile).
+    kProhibited = 0,
+
+    // Access has not been granted, but the user is free to grant access.
+    kAvailableButNotGranted = 1,
+
+    // Access has been granted by the user.
+    kAccessGranted = 2
+  };
+
   class Observer : public base::CheckedObserver {
    public:
     ~Observer() override = default;
 
-    // Called when notification access has changed; use HasAccessBeenGranted()
+    // Called when notification access has changed; use GetAccessStatus()
     // for the new status.
     virtual void OnNotificationAccessChanged() = 0;
   };
@@ -39,7 +55,7 @@
       delete;
   virtual ~NotificationAccessManager();
 
-  virtual bool HasAccessBeenGranted() const = 0;
+  virtual AccessStatus GetAccessStatus() const = 0;
 
   virtual bool HasNotificationSetupUiBeenDismissed() const = 0;
 
@@ -77,11 +93,9 @@
   friend class NotificationAccessManagerImplTest;
   friend class PhoneStatusProcessor;
 
-  // This only sets the internal state of the whether notification access has
-  // mode been enabled and does not send a request to set the state of the
-  // remote phone device.
-  virtual void SetHasAccessBeenGrantedInternal(
-      bool has_access_been_granted) = 0;
+  // Sets the internal AccessStatus but does not send a request for a new
+  // status to the remote phone device.
+  virtual void SetAccessStatusInternal(AccessStatus access_status) = 0;
 
   void OnSetupOperationDeleted(int operation_id);
 
@@ -91,6 +105,9 @@
   base::WeakPtrFactory<NotificationAccessManager> weak_ptr_factory_{this};
 };
 
+std::ostream& operator<<(std::ostream& stream,
+                         NotificationAccessManager::AccessStatus status);
+
 }  // namespace phonehub
 }  // namespace chromeos
 
diff --git a/chromeos/components/phonehub/notification_access_manager_impl.cc b/chromeos/components/phonehub/notification_access_manager_impl.cc
index 018d7b0a..0120165 100644
--- a/chromeos/components/phonehub/notification_access_manager_impl.cc
+++ b/chromeos/components/phonehub/notification_access_manager_impl.cc
@@ -17,7 +17,9 @@
 // static
 void NotificationAccessManagerImpl::RegisterPrefs(
     PrefRegistrySimple* registry) {
-  registry->RegisterBooleanPref(prefs::kNotificationAccessGranted, false);
+  registry->RegisterIntegerPref(
+      prefs::kNotificationAccessStatus,
+      static_cast<int>(AccessStatus::kAvailableButNotGranted));
   registry->RegisterBooleanPref(prefs::kHasDismissedSetupRequiredUi, false);
 }
 
@@ -50,25 +52,40 @@
   pref_service_->SetBoolean(prefs::kHasDismissedSetupRequiredUi, true);
 }
 
-bool NotificationAccessManagerImpl::HasAccessBeenGranted() const {
-  return pref_service_->GetBoolean(prefs::kNotificationAccessGranted);
+NotificationAccessManagerImpl::AccessStatus
+NotificationAccessManagerImpl::GetAccessStatus() const {
+  int status = pref_service_->GetInteger(prefs::kNotificationAccessStatus);
+  return static_cast<AccessStatus>(status);
 }
 
-void NotificationAccessManagerImpl::SetHasAccessBeenGrantedInternal(
-    bool has_access_been_granted) {
-  if (has_access_been_granted == HasAccessBeenGranted())
+void NotificationAccessManagerImpl::SetAccessStatusInternal(
+    AccessStatus access_status) {
+  if (access_status == GetAccessStatus())
     return;
 
-  PA_LOG(INFO) << "Notification access state has been set to: "
-               << has_access_been_granted;
+  PA_LOG(INFO) << "Notification access: " << GetAccessStatus() << " => "
+               << access_status;
 
-  pref_service_->SetBoolean(prefs::kNotificationAccessGranted,
-                            has_access_been_granted);
+  pref_service_->SetInteger(prefs::kNotificationAccessStatus,
+                            static_cast<int>(access_status));
   NotifyNotificationAccessChanged();
 
-  if (IsSetupOperationInProgress() && has_access_been_granted) {
-    SetNotificationSetupOperationStatus(
-        NotificationAccessSetupOperation::Status::kCompletedSuccessfully);
+  if (!IsSetupOperationInProgress())
+    return;
+
+  switch (access_status) {
+    case AccessStatus::kProhibited:
+      SetNotificationSetupOperationStatus(
+          NotificationAccessSetupOperation::Status::
+              kProhibitedFromProvidingAccess);
+      break;
+    case AccessStatus::kAccessGranted:
+      SetNotificationSetupOperationStatus(
+          NotificationAccessSetupOperation::Status::kCompletedSuccessfully);
+      break;
+    case AccessStatus::kAvailableButNotGranted:
+      // Intentionally blank; the operation status should not change.
+      break;
   }
 }
 
diff --git a/chromeos/components/phonehub/notification_access_manager_impl.h b/chromeos/components/phonehub/notification_access_manager_impl.h
index ed57552..8e039b7 100644
--- a/chromeos/components/phonehub/notification_access_manager_impl.h
+++ b/chromeos/components/phonehub/notification_access_manager_impl.h
@@ -20,9 +20,6 @@
 
 // Implements NotificationAccessManager by persisting the last-known
 // notification access value to user prefs.
-// TODO(khorimoto): Currently HasAccessBeenGranted() always returns false. Have
-// it return true once the phone has sent a message indicating that it has
-// granted access.
 class NotificationAccessManagerImpl : public NotificationAccessManager,
                                       public FeatureStatusProvider::Observer {
  public:
@@ -39,8 +36,8 @@
   friend class NotificationAccessManagerImplTest;
 
   // NotificationAccessManager:
-  bool HasAccessBeenGranted() const override;
-  void SetHasAccessBeenGrantedInternal(bool has_access_been_granted) override;
+  AccessStatus GetAccessStatus() const override;
+  void SetAccessStatusInternal(AccessStatus access_status) override;
   void OnSetupRequested() override;
 
   bool HasNotificationSetupUiBeenDismissed() const override;
diff --git a/chromeos/components/phonehub/notification_access_manager_impl_unittest.cc b/chromeos/components/phonehub/notification_access_manager_impl_unittest.cc
index 1606f67..5e5e4e7 100644
--- a/chromeos/components/phonehub/notification_access_manager_impl_unittest.cc
+++ b/chromeos/components/phonehub/notification_access_manager_impl_unittest.cc
@@ -73,9 +73,9 @@
 
   void TearDown() override { manager_->RemoveObserver(&fake_observer_); }
 
-  void Initialize(bool initial_has_access_been_granted) {
-    pref_service_.SetBoolean(prefs::kNotificationAccessGranted,
-                             initial_has_access_been_granted);
+  void Initialize(NotificationAccessManager::AccessStatus expected_status) {
+    pref_service_.SetInteger(prefs::kNotificationAccessStatus,
+                             static_cast<int>(expected_status));
     manager_ = std::make_unique<NotificationAccessManagerImpl>(
         &pref_service_, fake_feature_status_provider_.get(),
         fake_message_sender_.get(), fake_connection_scheduler_.get());
@@ -87,10 +87,11 @@
     return fake_delegate_.status();
   }
 
-  void VerifyNotificationAccessGrantedState(bool expected_value) {
-    EXPECT_EQ(expected_value,
-              pref_service_.GetBoolean(prefs::kNotificationAccessGranted));
-    EXPECT_EQ(expected_value, manager_->HasAccessBeenGranted());
+  void VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus expected_status) {
+    EXPECT_EQ(static_cast<int>(expected_status),
+              pref_service_.GetInteger(prefs::kNotificationAccessStatus));
+    EXPECT_EQ(expected_status, manager_->GetAccessStatus());
   }
 
   bool HasNotificationSetupUiBeenDismissed() {
@@ -107,8 +108,8 @@
     return manager_->IsSetupOperationInProgress();
   }
 
-  void SetHasAccessBeenGrantedInternal(bool has_access_been_granted) {
-    manager_->SetHasAccessBeenGrantedInternal(has_access_been_granted);
+  void SetAccessStatusInternal(NotificationAccessManager::AccessStatus status) {
+    manager_->SetAccessStatusInternal(status);
   }
 
   void SetFeatureStatus(FeatureStatus status) {
@@ -143,12 +144,12 @@
 TEST_F(NotificationAccessManagerImplTest, ShouldShowSetupRequiredUi) {
   // Notification setup is not dismissed initially even when access has been
   // granted.
-  Initialize(/*initial_has_access_been_granted=*/true);
+  Initialize(NotificationAccessManager::AccessStatus::kAccessGranted);
   EXPECT_FALSE(HasNotificationSetupUiBeenDismissed());
 
   // Notification setup is not dismissed initially when access has not been
   // granted.
-  Initialize(/*initial_has_access_been_granted=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
   EXPECT_FALSE(HasNotificationSetupUiBeenDismissed());
 
   // Simlulate dismissal of UI.
@@ -156,17 +157,18 @@
   EXPECT_TRUE(HasNotificationSetupUiBeenDismissed());
 
   // Dismissal value is persisted on initialization with access not granted.
-  Initialize(/*initial_has_access_been_granted=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
   EXPECT_TRUE(HasNotificationSetupUiBeenDismissed());
 
   // Dismissal value is persisted on initialization with access granted.
-  Initialize(/*initial_has_access_been_granted=*/true);
+  Initialize(NotificationAccessManager::AccessStatus::kAccessGranted);
   EXPECT_TRUE(HasNotificationSetupUiBeenDismissed());
 }
 
 TEST_F(NotificationAccessManagerImplTest, InitiallyGranted) {
-  Initialize(/*initial_has_access_been_granted=*/true);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/true);
+  Initialize(NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
 
   // Cannot start the notification access setup flow if access has already been
   // granted.
@@ -175,8 +177,9 @@
 }
 
 TEST_F(NotificationAccessManagerImplTest, OnFeatureStatusChanged) {
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   // Set initial state to disconnected.
   SetFeatureStatus(FeatureStatus::kEnabledButDisconnected);
@@ -210,8 +213,9 @@
   // Set initial state to disconnected.
   SetFeatureStatus(FeatureStatus::kEnabledButDisconnected);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   // Start a setup operation with enabled but disconnected status and access
   // not granted.
@@ -231,18 +235,56 @@
             GetNotifcationAccessSetupOperationStatus());
 
   // Simulate getting a response back from the phone.
-  SetHasAccessBeenGrantedInternal(/*has_access_been_granted=*/true);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/true);
+  SetAccessStatusInternal(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
   EXPECT_EQ(NotificationAccessSetupOperation::Status::kCompletedSuccessfully,
             GetNotifcationAccessSetupOperationStatus());
 }
 
+TEST_F(NotificationAccessManagerImplTest,
+       StartDisconnectedAndNoAccess_Prohibited) {
+  // Set initial state to disconnected.
+  SetFeatureStatus(FeatureStatus::kEnabledButDisconnected);
+
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+
+  // Start a setup operation with enabled but disconnected status and access
+  // not granted.
+  auto operation = StartSetupOperation();
+  EXPECT_TRUE(operation);
+  EXPECT_EQ(1u, GetNumScheduleConnectionNowCalls());
+
+  // Simulate changing states from connecting to connected.
+  SetFeatureStatus(FeatureStatus::kEnabledAndConnecting);
+  SetFeatureStatus(FeatureStatus::kEnabledAndConnected);
+
+  // Verify that the request message has been sent and our operation status
+  // is updated.
+  EXPECT_EQ(1u, GetNumShowNotificationAccessSetupRequestCount());
+  EXPECT_EQ(NotificationAccessSetupOperation::Status::
+                kSentMessageToPhoneAndWaitingForResponse,
+            GetNotifcationAccessSetupOperationStatus());
+
+  // Simulate getting a response back from the phone.
+  SetAccessStatusInternal(NotificationAccessManager::AccessStatus::kProhibited);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kProhibited);
+  EXPECT_EQ(
+      NotificationAccessSetupOperation::Status::kProhibitedFromProvidingAccess,
+      GetNotifcationAccessSetupOperationStatus());
+}
+
 TEST_F(NotificationAccessManagerImplTest, StartConnectingAndNoAccess) {
   // Set initial state to connecting.
   SetFeatureStatus(FeatureStatus::kEnabledAndConnecting);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   // Start a setup operation with enabled and connecting status and access
   // not granted.
@@ -260,8 +302,10 @@
             GetNotifcationAccessSetupOperationStatus());
 
   // Simulate getting a response back from the phone.
-  SetHasAccessBeenGrantedInternal(/*has_access_been_granted=*/true);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/true);
+  SetAccessStatusInternal(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
   EXPECT_EQ(NotificationAccessSetupOperation::Status::kCompletedSuccessfully,
             GetNotifcationAccessSetupOperationStatus());
 }
@@ -270,8 +314,9 @@
   // Set initial state to connected.
   SetFeatureStatus(FeatureStatus::kEnabledAndConnected);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   // Start a setup operation with enabled and connected status and access
   // not granted.
@@ -286,8 +331,10 @@
             GetNotifcationAccessSetupOperationStatus());
 
   // Simulate getting a response back from the phone.
-  SetHasAccessBeenGrantedInternal(/*has_access_been_granted=*/true);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/true);
+  SetAccessStatusInternal(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
   EXPECT_EQ(NotificationAccessSetupOperation::Status::kCompletedSuccessfully,
             GetNotifcationAccessSetupOperationStatus());
 }
@@ -296,8 +343,9 @@
   // Set initial state to connecting.
   SetFeatureStatus(FeatureStatus::kEnabledAndConnecting);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   auto operation = StartSetupOperation();
   EXPECT_TRUE(operation);
@@ -312,8 +360,9 @@
   // Simulate connected state.
   SetFeatureStatus(FeatureStatus::kEnabledAndConnected);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   auto operation = StartSetupOperation();
   EXPECT_TRUE(operation);
@@ -330,8 +379,9 @@
   // Simulate connected state.
   SetFeatureStatus(FeatureStatus::kEnabledAndConnected);
 
-  Initialize(/*initial_has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  Initialize(NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 
   auto operation = StartSetupOperation();
   EXPECT_TRUE(operation);
@@ -345,12 +395,27 @@
 }
 
 TEST_F(NotificationAccessManagerImplTest, FlipAccessGrantedToNotGranted) {
-  Initialize(/*initial_has_access_been_granted=*/true);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/true);
+  Initialize(NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
 
   // Simulate flipping the access state to no granted.
-  SetHasAccessBeenGrantedInternal(/*has_access_been_granted=*/false);
-  VerifyNotificationAccessGrantedState(/*expected_value=*/false);
+  SetAccessStatusInternal(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAvailableButNotGranted);
 }
+
+TEST_F(NotificationAccessManagerImplTest, FlipAccessGrantedToProhibited) {
+  Initialize(NotificationAccessManager::AccessStatus::kAccessGranted);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kAccessGranted);
+
+  // Simulate flipping the access state to prohibited.
+  SetAccessStatusInternal(NotificationAccessManager::AccessStatus::kProhibited);
+  VerifyNotificationAccessGrantedState(
+      NotificationAccessManager::AccessStatus::kProhibited);
+}
+
 }  // namespace phonehub
 }  // namespace chromeos
diff --git a/chromeos/components/phonehub/notification_access_setup_operation.cc b/chromeos/components/phonehub/notification_access_setup_operation.cc
index d11e607..361c13c0 100644
--- a/chromeos/components/phonehub/notification_access_setup_operation.cc
+++ b/chromeos/components/phonehub/notification_access_setup_operation.cc
@@ -16,11 +16,13 @@
 // Status values which are considered "final" - i.e., once the status of an
 // operation changes to one of these values, the operation has completed. These
 // status values indicate either a success or a fatal error.
-constexpr std::array<NotificationAccessSetupOperation::Status, 3>
+constexpr std::array<NotificationAccessSetupOperation::Status, 4>
     kOperationFinishedStatus{
         NotificationAccessSetupOperation::Status::kTimedOutConnecting,
         NotificationAccessSetupOperation::Status::kConnectionDisconnected,
         NotificationAccessSetupOperation::Status::kCompletedSuccessfully,
+        NotificationAccessSetupOperation::Status::
+            kProhibitedFromProvidingAccess,
     };
 
 }  // namespace
@@ -66,6 +68,10 @@
     case NotificationAccessSetupOperation::Status::kCompletedSuccessfully:
       stream << "[Completed successfully]";
       break;
+    case NotificationAccessSetupOperation::Status::
+        kProhibitedFromProvidingAccess:
+      stream << "[Prohibited from providing access]";
+      break;
   }
 
   return stream;
diff --git a/chromeos/components/phonehub/notification_access_setup_operation.h b/chromeos/components/phonehub/notification_access_setup_operation.h
index b55e68f..2b450d4 100644
--- a/chromeos/components/phonehub/notification_access_setup_operation.h
+++ b/chromeos/components/phonehub/notification_access_setup_operation.h
@@ -48,6 +48,10 @@
 
     // The user has completed the phone-side opt-in flow.
     kCompletedSuccessfully = 5,
+
+    // The user's phone is prohibited from granting notification access (e.g.,
+    // the user could be using a Work Profile).
+    kProhibitedFromProvidingAccess = 6
   };
 
   // Returns true if the provided status is the final one for this operation,
diff --git a/chromeos/components/phonehub/phone_status_processor.cc b/chromeos/components/phonehub/phone_status_processor.cc
index 1da7ab7..2e4f06a1 100644
--- a/chromeos/components/phonehub/phone_status_processor.cc
+++ b/chromeos/components/phonehub/phone_status_processor.cc
@@ -108,6 +108,21 @@
   }
 }
 
+NotificationAccessManager::AccessStatus ComputeNotificationAccessState(
+    const proto::PhoneProperties& phone_properties) {
+  // If the user has a Work Profile active, notification access is not allowed
+  // by Android. See https://crbug.com/1155151.
+  if (phone_properties.profile_type() == proto::ProfileType::WORK_PROFILE)
+    return NotificationAccessManager::AccessStatus::kProhibited;
+
+  if (phone_properties.notification_access_state() ==
+      proto::NotificationAccessState::ACCESS_GRANTED) {
+    return NotificationAccessManager::AccessStatus::kAccessGranted;
+  }
+
+  return NotificationAccessManager::AccessStatus::kAvailableButNotGranted;
+}
+
 base::Optional<Notification> ProcessNotificationProto(
     const proto::Notification& proto) {
   // Only process notifications that are messaging apps with inline-replies.
@@ -219,9 +234,8 @@
           proto::NotificationMode::DO_NOT_DISTURB_ON,
       phone_properties.profile_type() != proto::ProfileType::WORK_PROFILE);
 
-  notification_access_manager_->SetHasAccessBeenGrantedInternal(
-      phone_properties.notification_access_state() ==
-      proto::NotificationAccessState::ACCESS_GRANTED);
+  notification_access_manager_->SetAccessStatusInternal(
+      ComputeNotificationAccessState(phone_properties));
 
   find_my_device_controller_->SetIsPhoneRingingInternal(
       phone_properties.ring_status() == proto::FindMyDeviceRingStatus::RINGING);
diff --git a/chromeos/components/phonehub/phone_status_processor_unittest.cc b/chromeos/components/phonehub/phone_status_processor_unittest.cc
index d875b674..04ea4bd 100644
--- a/chromeos/components/phonehub/phone_status_processor_unittest.cc
+++ b/chromeos/components/phonehub/phone_status_processor_unittest.cc
@@ -111,7 +111,7 @@
   expected_phone_properties->set_profile_type(
       proto::ProfileType::DEFAULT_PROFILE);
   expected_phone_properties->set_notification_access_state(
-      proto::NotificationAccessState::ACCESS_GRANTED);
+      proto::NotificationAccessState::ACCESS_NOT_GRANTED);
   expected_phone_properties->set_ring_status(
       proto::FindMyDeviceRingStatus::RINGING);
   expected_phone_properties->set_battery_percentage(24u);
@@ -142,7 +142,8 @@
   EXPECT_TRUE(fake_do_not_disturb_controller_->CanRequestNewDndState());
   EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
             fake_find_my_device_controller_->GetPhoneRingingStatus());
-  EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
+  EXPECT_EQ(NotificationAccessManager::AccessStatus::kAvailableButNotGranted,
+            fake_notification_access_manager_->GetAccessStatus());
 
   base::Optional<PhoneStatusModel> phone_status_model =
       mutable_phone_model_->phone_status_model();
@@ -206,7 +207,8 @@
   EXPECT_FALSE(fake_do_not_disturb_controller_->CanRequestNewDndState());
   EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
             fake_find_my_device_controller_->GetPhoneRingingStatus());
-  EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
+  EXPECT_EQ(NotificationAccessManager::AccessStatus::kProhibited,
+            fake_notification_access_manager_->GetAccessStatus());
 
   base::Optional<PhoneStatusModel> phone_status_model =
       mutable_phone_model_->phone_status_model();
@@ -220,18 +222,21 @@
   EXPECT_EQ(PhoneStatusModel::MobileStatus::kSimWithReception,
             phone_status_model->mobile_status());
 
-  // Update with one removed notification.
+  // Update with one removed notification and a default profile.
   expected_update.add_removed_notification_ids(0u);
+  expected_update.mutable_properties()->set_profile_type(
+      proto::ProfileType::DEFAULT_PROFILE);
   fake_message_receiver_->NotifyPhoneStatusUpdateReceived(expected_update);
 
   EXPECT_EQ(0u, fake_notification_manager_->num_notifications());
   EXPECT_EQ(base::UTF8ToUTF16(test_remote_device_.name()),
             *mutable_phone_model_->phone_name());
   EXPECT_TRUE(fake_do_not_disturb_controller_->IsDndEnabled());
-  EXPECT_FALSE(fake_do_not_disturb_controller_->CanRequestNewDndState());
+  EXPECT_TRUE(fake_do_not_disturb_controller_->CanRequestNewDndState());
   EXPECT_EQ(FindMyDeviceController::Status::kRingingOn,
             fake_find_my_device_controller_->GetPhoneRingingStatus());
-  EXPECT_TRUE(fake_notification_access_manager_->HasAccessBeenGranted());
+  EXPECT_EQ(NotificationAccessManager::AccessStatus::kAccessGranted,
+            fake_notification_access_manager_->GetAccessStatus());
 
   phone_status_model = mutable_phone_model_->phone_status_model();
   EXPECT_EQ(PhoneStatusModel::ChargingState::kChargingAc,
diff --git a/chromeos/components/phonehub/pref_names.cc b/chromeos/components/phonehub/pref_names.cc
index b121fd3..45a4c73 100644
--- a/chromeos/components/phonehub/pref_names.cc
+++ b/chromeos/components/phonehub/pref_names.cc
@@ -8,9 +8,11 @@
 namespace phonehub {
 namespace prefs {
 
-// Whether notification access had been granted by the user on their phone.
-const char kNotificationAccessGranted[] =
-    "cros.phonehub.notification_access_granted";
+// The last provided notification access status provided by the phone. This pref
+// stores the numerical value associated with the
+// NotificationAccessManager::AccessStatus enum.
+const char kNotificationAccessStatus[] =
+    "cros.phonehub.notification_access_status";
 
 // Whether user has completed onboarding and dismissed the UI before or if
 // the user has already gone through the onboarding process and has enabled the
diff --git a/chromeos/components/phonehub/pref_names.h b/chromeos/components/phonehub/pref_names.h
index 7357841..971e702 100644
--- a/chromeos/components/phonehub/pref_names.h
+++ b/chromeos/components/phonehub/pref_names.h
@@ -9,7 +9,7 @@
 namespace phonehub {
 namespace prefs {
 
-extern const char kNotificationAccessGranted[];
+extern const char kNotificationAccessStatus[];
 extern const char kHideOnboardingUi[];
 extern const char kIsAwaitingVerifiedHost[];
 extern const char kHasDismissedSetupRequiredUi[];
diff --git a/chromeos/components/quick_answers/search_result_loader.cc b/chromeos/components/quick_answers/search_result_loader.cc
index 28f2195..dfaa8f30 100644
--- a/chromeos/components/quick_answers/search_result_loader.cc
+++ b/chromeos/components/quick_answers/search_result_loader.cc
@@ -30,9 +30,6 @@
 //   "client_id": {
 //     "client_type": "EXPERIMENTAL"
 //   }
-//   "options": {
-//     "page_size": "1"
-//   }
 // }
 //
 // Which is:
@@ -41,17 +38,12 @@
 //      "raw_query": STRING
 //   "client_id": DICT
 //       "client_type": STRING
-//   "options": DICT
-//       "page_size": STRING
 
 constexpr base::StringPiece kQueryKey = "query";
 constexpr base::StringPiece kRawQueryKey = "rawQuery";
 constexpr base::StringPiece kClientTypeKey = "clientType";
 constexpr base::StringPiece kClientIdKey = "clientId";
 constexpr base::StringPiece kClientType = "QUICK_ANSWERS_CROS";
-constexpr base::StringPiece kPageSizeKey = "pageSize";
-constexpr base::StringPiece kOptionsKey = "options";
-constexpr base::StringPiece kPageSize = "1";
 
 std::string BuildSearchRequestPayload(const std::string& selected_text) {
   Value payload(Value::Type::DICTIONARY);
@@ -65,10 +57,6 @@
   client_id.SetKey(kClientTypeKey, Value(kClientType));
   payload.SetKey(kClientIdKey, std::move(client_id));
 
-  Value options(Value::Type::DICTIONARY);
-  options.SetKey(kPageSizeKey, Value(kPageSize));
-  payload.SetKey(kOptionsKey, std::move(options));
-
   std::string request_payload_str;
   base::JSONWriter::Write(payload, &request_payload_str);
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 7ae16e8..5e2b1ce 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -394,6 +394,10 @@
 const base::Feature kImeMojoDecoder{"ImeMojoDecoder",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable or disable system emoji picker.
+const base::Feature kImeSystemEmojiPicker{"SystemEmojiPicker",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables view-based version of multiprofile login, as opposed to Web UI one.
 const base::Feature kViewBasedMultiprofileLogin{
     "ViewBasedMultiprofileLogin", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index e86ce82..73761be 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -182,6 +182,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kImeOptionsInSettings;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kImeSystemEmojiPicker;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kVirtualKeyboardFloatingDefault;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kInstantTethering;
diff --git a/chromeos/dbus/tpm_manager/fake_tpm_manager_client.cc b/chromeos/dbus/tpm_manager/fake_tpm_manager_client.cc
index 59da171..182b7dd 100644
--- a/chromeos/dbus/tpm_manager/fake_tpm_manager_client.cc
+++ b/chromeos/dbus/tpm_manager/fake_tpm_manager_client.cc
@@ -90,7 +90,7 @@
   return &nonsensitive_status_reply_;
 }
 
-void FakeTpmManagerClient::set_non_nonsensitive_status_dbus_erorr_count(
+void FakeTpmManagerClient::set_non_nonsensitive_status_dbus_error_count(
     int count) {
   nonsensitive_status_dbus_error_count_ = count;
 }
diff --git a/chromeos/dbus/tpm_manager/fake_tpm_manager_client.h b/chromeos/dbus/tpm_manager/fake_tpm_manager_client.h
index eb4e261..fb295fd 100644
--- a/chromeos/dbus/tpm_manager/fake_tpm_manager_client.h
+++ b/chromeos/dbus/tpm_manager/fake_tpm_manager_client.h
@@ -48,7 +48,7 @@
   // TpmManagerClient::TestInterface:
   ::tpm_manager::GetTpmNonsensitiveStatusReply*
   mutable_nonsensitive_status_reply() override;
-  void set_non_nonsensitive_status_dbus_erorr_count(int count) override;
+  void set_non_nonsensitive_status_dbus_error_count(int count) override;
   ::tpm_manager::GetVersionInfoReply* mutable_version_info_reply() override;
   int clear_stored_owner_password_count() const override;
   void EmitOwnershipTakenSignal() override;
diff --git a/chromeos/dbus/tpm_manager/tpm_manager_client.h b/chromeos/dbus/tpm_manager/tpm_manager_client.h
index 151a0079..516389e 100644
--- a/chromeos/dbus/tpm_manager/tpm_manager_client.h
+++ b/chromeos/dbus/tpm_manager/tpm_manager_client.h
@@ -52,7 +52,7 @@
     mutable_nonsensitive_status_reply() = 0;
     // Sets how many times the `GetTpmNonsensitiveStatus()` returns D-Bus error
     // before it works normally.
-    virtual void set_non_nonsensitive_status_dbus_erorr_count(int count) = 0;
+    virtual void set_non_nonsensitive_status_dbus_error_count(int count) = 0;
     // Gets a mutable reply that is returned when `GetVersionInfo()` is called.
     virtual ::tpm_manager::GetVersionInfoReply*
     mutable_version_info_reply() = 0;
diff --git a/chromeos/geolocation/simple_geolocation_request.cc b/chromeos/geolocation/simple_geolocation_request.cc
index 92b44e8..0292549 100644
--- a/chromeos/geolocation/simple_geolocation_request.cc
+++ b/chromeos/geolocation/simple_geolocation_request.cc
@@ -71,7 +71,7 @@
 
 // Error object and its contents.
 constexpr char kErrorString[] = "error";
-// "errors" array in "erorr" object is ignored.
+// "errors" array in "error" object is ignored.
 constexpr char kCodeString[] = "code";
 constexpr char kMessageString[] = "message";
 
diff --git a/chromeos/services/ime/BUILD.gn b/chromeos/services/ime/BUILD.gn
index 5bae65a..1652e12 100644
--- a/chromeos/services/ime/BUILD.gn
+++ b/chromeos/services/ime/BUILD.gn
@@ -36,8 +36,6 @@
   sources = [
     "decoder/decoder_engine.cc",
     "decoder/decoder_engine.h",
-    "decoder/downloader_impl.cc",
-    "decoder/downloader_impl.h",
     "decoder/proto_conversion.cc",
     "decoder/proto_conversion.h",
     "decoder/system_engine.cc",
diff --git a/chromeos/services/ime/decoder/downloader_impl.cc b/chromeos/services/ime/decoder/downloader_impl.cc
deleted file mode 100644
index 90b66fdd..0000000
--- a/chromeos/services/ime/decoder/downloader_impl.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/services/ime/decoder/downloader_impl.h"
-
-namespace chromeos {
-namespace ime {
-
-DownloaderImpl::DownloaderImpl() {
-  // TODO(crbug/946913): Implement this.
-}
-
-DownloaderImpl::~DownloaderImpl() {
-  // TODO(crbug/946913): Implement this.
-}
-
-int DownloaderImpl::DownloadToFile(const char* url,
-                                   const DownloadOptions& options,
-                                   const char* file_path,
-                                   ImeCrosDownloadCallback callback) {
-  // TODO(crbug/946913): Implement this.
-  return 0;
-}
-
-void DownloaderImpl::Cancel(int request_id) {
-  // TODO(crbug/946913): Implement this.
-}
-
-}  // namespace ime
-}  // namespace chromeos
diff --git a/chromeos/services/ime/decoder/downloader_impl.h b/chromeos/services/ime/decoder/downloader_impl.h
deleted file mode 100644
index 90c2d8e..0000000
--- a/chromeos/services/ime/decoder/downloader_impl.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_SERVICES_IME_DECODER_DOWNLOADER_IMPL_H_
-#define CHROMEOS_SERVICES_IME_DECODER_DOWNLOADER_IMPL_H_
-
-#include "base/macros.h"
-#include "chromeos/services/ime/public/cpp/shared_lib/interfaces.h"
-
-namespace chromeos {
-namespace ime {
-
-class DownloaderImpl : public ImeCrosDownloader {
- public:
-  explicit DownloaderImpl();
-  ~DownloaderImpl() override;
-
-  int DownloadToFile(const char* url,
-                     const DownloadOptions& options,
-                     const char* file_path,
-                     ImeCrosDownloadCallback callback) override;
-
-  void Cancel(int request_id) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DownloaderImpl);
-};
-
-}  // namespace ime
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_IME_DECODER_DOWNLOADER_IMPL_H_
diff --git a/chromeos/tpm/tpm_token_info_getter_unittest.cc b/chromeos/tpm/tpm_token_info_getter_unittest.cc
index bb94de6d..5a07bf2 100644
--- a/chromeos/tpm/tpm_token_info_getter_unittest.cc
+++ b/chromeos/tpm/tpm_token_info_getter_unittest.cc
@@ -321,7 +321,7 @@
 TEST_F(SystemTPMTokenInfoGetterTest, TpmEnabledCallFails) {
   chromeos::TpmManagerClient::Get()
       ->GetTestInterface()
-      ->set_non_nonsensitive_status_dbus_erorr_count(1);
+      ->set_non_nonsensitive_status_dbus_error_count(1);
 
   bool completed = false;
   base::Optional<TpmTokenInfo> result;
@@ -398,7 +398,7 @@
 TEST_F(SystemTPMTokenInfoGetterTest, RetryDelaysIncreaseExponentially) {
   chromeos::TpmManagerClient::Get()
       ->GetTestInterface()
-      ->set_non_nonsensitive_status_dbus_erorr_count(2);
+      ->set_non_nonsensitive_status_dbus_error_count(2);
   cryptohome_client_->set_get_tpm_token_info_failure_count(1);
   cryptohome_client_->set_get_tpm_token_info_not_set_count(3);
 
@@ -427,7 +427,7 @@
 TEST_F(SystemTPMTokenInfoGetterTest, RetryDelayBounded) {
   chromeos::TpmManagerClient::Get()
       ->GetTestInterface()
-      ->set_non_nonsensitive_status_dbus_erorr_count(4);
+      ->set_non_nonsensitive_status_dbus_error_count(4);
   cryptohome_client_->set_get_tpm_token_info_failure_count(5);
   cryptohome_client_->set_get_tpm_token_info_not_set_count(6);
 
diff --git a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/FileEnumWorkerTask.java b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/FileEnumWorkerTask.java
index c161775..ba2e6de 100644
--- a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/FileEnumWorkerTask.java
+++ b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/FileEnumWorkerTask.java
@@ -10,10 +10,10 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.provider.MediaStore;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.AsyncTask;
@@ -108,8 +108,9 @@
         List<PickerBitmap> pickerBitmaps = new ArrayList<>();
 
         // The DATA column is deprecated in the Android Q SDK. Replaced by relative_path.
-        String directoryColumnName =
-                BuildInfo.isAtLeastQ() ? "relative_path" : MediaStore.Files.FileColumns.DATA;
+        String directoryColumnName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+                ? "relative_path"
+                : MediaStore.Files.FileColumns.DATA;
         final String[] selectColumns = {
                 MediaStore.Files.FileColumns._ID,
                 MediaStore.Files.FileColumns.DATE_ADDED,
@@ -138,7 +139,7 @@
         String downloadsDir = Environment.DIRECTORY_DOWNLOADS;
         // Files downloaded from the user's Google Photos library go to a Restored folder.
         String restoredDir = Environment.DIRECTORY_DCIM + "/Restored";
-        if (!BuildInfo.isAtLeastQ()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             cameraDir = Environment.getExternalStoragePublicDirectory(cameraDir).toString();
             picturesDir = Environment.getExternalStoragePublicDirectory(picturesDir).toString();
             downloadsDir = Environment.getExternalStoragePublicDirectory(downloadsDir).toString();
diff --git a/components/policy/core/common/cloud/cloud_policy_client.cc b/components/policy/core/common/cloud/cloud_policy_client.cc
index 08619ac5..fcb8107 100644
--- a/components/policy/core/common/cloud/cloud_policy_client.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client.cc
@@ -720,9 +720,10 @@
     CloudPolicyClient::StatusCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK(is_registered());
-  // This condition is wrong in case of Attestation enrollment
-  // (https://crbug.com/942013).
-  // DCHECK(auth->has_oauth_token() || auth->has_enrollment_token());
+  // This request only works with an OAuth token identifying a user, because
+  // DMServer will resolve that user and check if they have permissions to
+  // update the device's attributes.
+  DCHECK(auth->has_oauth_token());
 
   const bool has_oauth_token = auth->has_oauth_token();
   const std::string oauth_token =
diff --git a/content/browser/media/media_devices_permission_checker.cc b/content/browser/media/media_devices_permission_checker.cc
index d1f4fd55..e33334b 100644
--- a/content/browser/media/media_devices_permission_checker.cc
+++ b/content/browser/media/media_devices_permission_checker.cc
@@ -155,10 +155,9 @@
     int render_frame_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 #if defined(OS_ANDROID)
-  // The PTZ permission is automatically granted on Android, regardless of the
-  // MediaCapturePanTilt Blink feature state. This way, zoom is not initially
-  // empty in ImageCapture. It is safe to do so because pan and tilt are not
-  // supported on Android.
+  // The PTZ permission is automatically granted on Android. This way, zoom is
+  // not initially empty in ImageCapture. It is safe to do so because pan and
+  // tilt are not supported on Android.
   return true;
 #else
   RenderFrameHostImpl* frame_host =
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 1d45493..6959386 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -121,12 +121,8 @@
   void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
   }
-  void RecordManipulationTypeCounts(cc::ManipulationInfo args) override {}
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override {}
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override {}
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override {}
   void RequestNewLayerTreeFrameSink() override;
   void DidInitializeLayerTreeFrameSink() override;
   void DidFailToInitializeLayerTreeFrameSink() override;
diff --git a/content/browser/renderer_host/navigation_controller_impl_unittest.cc b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
index 8501f5b..df9e116 100644
--- a/content/browser/renderer_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
@@ -710,37 +710,6 @@
   EXPECT_GE(controller.GetVisibleEntry()->GetTimestamp(), timestamp);
 }
 
-// Load the same page twice, once as a GET and once as a POST.
-// We should update the post state on the NavigationEntry.
-TEST_F(NavigationControllerTest, LoadURL_SamePage_DifferentMethod) {
-  NavigationControllerImpl& controller = controller_impl();
-
-  const GURL url1("http://foo1");
-
-  auto navigation1 =
-      NavigationSimulatorImpl::CreateBrowserInitiated(url1, contents());
-  navigation1->SetIsPostWithId(123);
-  navigation1->Commit();
-
-  // The post data should be visible.
-  NavigationEntry* entry = controller.GetVisibleEntry();
-  ASSERT_TRUE(entry);
-  EXPECT_TRUE(entry->GetHasPostData());
-  EXPECT_EQ(entry->GetPostID(), 123);
-
-  auto navigation2 =
-      NavigationSimulatorImpl::CreateBrowserInitiated(url1, contents());
-  navigation2->set_did_create_new_entry(false);
-  navigation2->Commit();
-
-  // We should not have produced a new session history entry.
-  ASSERT_EQ(controller.GetVisibleEntry(), entry);
-
-  // The post data should have been cleared due to the GET.
-  EXPECT_FALSE(entry->GetHasPostData());
-  EXPECT_EQ(entry->GetPostID(), -1);
-}
-
 // Tests loading a URL but discarding it before the load commits.
 TEST_F(NavigationControllerTest, LoadURL_Discarded) {
   NavigationControllerImpl& controller = controller_impl();
@@ -1875,7 +1844,6 @@
 
   // ... and now the renderer sends a commit for the first navigation.
   LoadCommittedDetailsObserver observer(contents());
-  navigation1->set_intended_as_new_entry(true);
   navigation1->Commit();
   EXPECT_EQ(NAVIGATION_TYPE_EXISTING_PAGE, observer.navigation_type());
 }
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 52dfc50e1..bd4f5fef 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9562,6 +9562,16 @@
                         has_original_url
                             ? GetSiteInstance()->original_url().IsAboutSrcdoc()
                             : false);
+
+  // These DCHECKs ensure that tests will fail if we got here, as
+  // DumpWithoutCrashing won't fail tests.
+  // TODO(rakina): Add DCHECK for url_is_unreachable.
+  DCHECK_EQ(request->commit_params().intended_as_new_entry,
+            params.intended_as_new_entry);
+  DCHECK_EQ(request->common_params().method, params.method);
+  DCHECK_EQ(browser_post_id, params.post_id);
+  DCHECK_EQ(browser_is_overriding_user_agent, params.is_overriding_user_agent);
+  DCHECK(base_url_expectations_match);
   base::debug::DumpWithoutCrashing();
 }
 
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
index e2a0c89e..fcb9756 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -556,6 +556,7 @@
     DidFail(status);
     return;
   }
+
   if (!IsEventDispatched()) {
     DispatchFetchEvent();
   }
@@ -781,13 +782,16 @@
   return request_.is_null();
 }
 
+// static
 void ServiceWorkerFetchDispatcher::OnFetchEventFinished(
+    base::WeakPtr<ServiceWorkerFetchDispatcher> fetch_dispatcher,
     ServiceWorkerVersion* version,
     int event_finish_id,
     scoped_refptr<URLLoaderAssets> url_loader_assets,
     blink::mojom::ServiceWorkerEventStatus status) {
-  if (status == blink::mojom::ServiceWorkerEventStatus::TIMEOUT) {
-    DidFail(blink::ServiceWorkerStatusCode::kErrorTimeout);
+  if (fetch_dispatcher &&
+      status == blink::mojom::ServiceWorkerEventStatus::TIMEOUT) {
+    fetch_dispatcher->DidFail(blink::ServiceWorkerStatusCode::kErrorTimeout);
   }
   version->FinishRequest(
       event_finish_id,
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.h b/content/browser/service_worker/service_worker_fetch_dispatcher.h
index 0da8bcb..d436dbf 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.h
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.h
@@ -75,6 +75,8 @@
   // the version is activated and running.
   void Run();
 
+  bool FetchCallbackIsNull() { return fetch_callback_.is_null(); }
+
  private:
   class ResponseCallback;
   class URLLoaderAssets;
@@ -101,10 +103,12 @@
   // are settled. This function is called once the renderer signals that
   // happened. |fetch_callback_| can run before this, once respondWith() is
   // settled.
-  void OnFetchEventFinished(ServiceWorkerVersion* version,
-                            int event_finish_id,
-                            scoped_refptr<URLLoaderAssets> url_loader_assets,
-                            blink::mojom::ServiceWorkerEventStatus status);
+  static void OnFetchEventFinished(
+      base::WeakPtr<ServiceWorkerFetchDispatcher> fetch_dispatcher,
+      ServiceWorkerVersion* version,
+      int event_finish_id,
+      scoped_refptr<URLLoaderAssets> url_loader_assets,
+      blink::mojom::ServiceWorkerEventStatus status);
 
   ServiceWorkerMetrics::EventType GetEventType() const;
 
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher_browsertest.cc b/content/browser/service_worker/service_worker_fetch_dispatcher_browsertest.cc
new file mode 100644
index 0000000..5836c0a
--- /dev/null
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher_browsertest.cc
@@ -0,0 +1,270 @@
+// 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/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/scoped_observation.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_run_loop_timeout.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
+#include "content/browser/service_worker/service_worker_test_utils.h"
+#include "content/browser/service_worker/service_worker_version.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/service_worker_context_observer.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
+#include "third_party/blink/public/common/service_worker/service_worker_type_converters.h"
+
+namespace content {
+
+namespace {
+
+struct FetchResult {
+  blink::ServiceWorkerStatusCode status;
+  ServiceWorkerFetchDispatcher::FetchEventResult result;
+  network::mojom::FetchResponseSource response_source;
+  uint16_t response_status_code;
+
+  bool operator==(const FetchResult& other) const {
+    return status == other.status && result == other.result &&
+           response_source == other.response_source &&
+           response_status_code == other.response_status_code;
+  }
+};
+
+const FetchResult kNetworkCompleted = FetchResult{
+    blink::ServiceWorkerStatusCode::kOk,
+    ServiceWorkerFetchDispatcher::FetchEventResult::kGotResponse,
+    network::mojom::FetchResponseSource::kNetwork,
+    200,
+};
+
+const FetchResult kTimeout = FetchResult{
+    blink::ServiceWorkerStatusCode::kErrorTimeout,
+    ServiceWorkerFetchDispatcher::FetchEventResult::kShouldFallback,
+    network::mojom::FetchResponseSource::kUnspecified,
+    0,
+};
+
+}  // namespace
+
+// An observer that waits for the service worker to be running.
+class WorkerRunningStatusObserver : public ServiceWorkerContextObserver {
+ public:
+  explicit WorkerRunningStatusObserver(ServiceWorkerContext* context) {
+    scoped_context_observation_.Observe(context);
+  }
+
+  ~WorkerRunningStatusObserver() override = default;
+
+  int64_t version_id() { return version_id_; }
+
+  void WaitUntilRunning() {
+    if (version_id_ == blink::mojom::kInvalidServiceWorkerVersionId)
+      run_loop_.Run();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  base::ScopedObservation<ServiceWorkerContext, ServiceWorkerContextObserver>
+      scoped_context_observation_{this};
+  int64_t version_id_ = blink::mojom::kInvalidServiceWorkerVersionId;
+
+  DISALLOW_COPY_AND_ASSIGN(WorkerRunningStatusObserver);
+};
+
+// An observer that waits until all inflight events complete.
+class NoWorkObserver : public ServiceWorkerVersion::Observer {
+ public:
+  explicit NoWorkObserver(base::OnceClosure closure)
+      : closure_(std::move(closure)) {}
+
+  void OnNoWork(ServiceWorkerVersion* version) override {
+    EXPECT_TRUE(version->HasNoWork());
+    DCHECK(closure_);
+    std::move(closure_).Run();
+  }
+
+ private:
+  base::OnceClosure closure_;
+};
+
+class ServiceWorkerFetchDispatcherBrowserTest : public ContentBrowserTest {
+ public:
+  using self = ServiceWorkerFetchDispatcherBrowserTest;
+
+  ~ServiceWorkerFetchDispatcherBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+
+    StoragePartition* partition = BrowserContext::GetDefaultStoragePartition(
+        shell()->web_contents()->GetBrowserContext());
+    wrapper_ = base::WrapRefCounted(static_cast<ServiceWorkerContextWrapper*>(
+        partition->GetServiceWorkerContext()));
+  }
+
+  void TearDownOnMainThread() override { wrapper_.reset(); }
+
+  std::unique_ptr<ServiceWorkerFetchDispatcher> CreateFetchDispatcher(
+      base::OnceClosure done,
+      const std::string& path,
+      bool is_offline_capability_check,
+      ServiceWorkerVersion* version,
+      FetchResult* result) {
+    GURL url = embedded_test_server()->GetURL(path);
+    network::mojom::RequestDestination destination =
+        network::mojom::RequestDestination::kDocument;
+    ServiceWorkerFetchDispatcher::FetchCallback fetch_callback =
+        CreateResponseReceiver(std::move(done), result);
+
+    auto request = blink::mojom::FetchAPIRequest::New();
+    request->url = url;
+    request->method = "GET";
+    request->is_main_resource_load = true;
+
+    return std::make_unique<ServiceWorkerFetchDispatcher>(
+        std::move(request), destination, std::string() /* client_id */, version,
+        base::DoNothing() /* prepare_result */, std::move(fetch_callback),
+        is_offline_capability_check);
+  }
+
+  // Contrary to the style guide, the output parameter of this function comes
+  // before input parameters so Bind can be used on it to create a FetchCallback
+  // to pass to DispatchFetchEvent.
+  void ReceiveFetchResult(
+      base::OnceClosure quit,
+      FetchResult* out_result,
+      blink::ServiceWorkerStatusCode actual_status,
+      ServiceWorkerFetchDispatcher::FetchEventResult actual_result,
+      blink::mojom::FetchAPIResponsePtr actual_response,
+      blink::mojom::ServiceWorkerStreamHandlePtr /* stream */,
+      blink::mojom::ServiceWorkerFetchEventTimingPtr /* timing */,
+      scoped_refptr<ServiceWorkerVersion> worker) {
+    out_result->status = actual_status;
+    out_result->result = actual_result;
+    out_result->response_source = actual_response->response_source;
+    out_result->response_status_code = actual_response->status_code;
+    if (!quit.is_null())
+      std::move(quit).Run();
+  }
+
+  ServiceWorkerFetchDispatcher::FetchCallback CreateResponseReceiver(
+      base::OnceClosure quit,
+      FetchResult* result) {
+    return base::BindOnce(&self::ReceiveFetchResult, base::Unretained(this),
+                          std::move(quit), result);
+  }
+
+  // Starts the test server and navigates the renderer to an empty page. Call
+  // this after adding all request handlers to the test server. Adding handlers
+  // after the test server has started is not allowed.
+  void StartServerAndNavigateToSetup() {
+    embedded_test_server()->StartAcceptingConnections();
+
+    // Navigate to the page to set up a renderer page (where we can embed
+    // a worker).
+    NavigateToURLBlockUntilNavigationsComplete(
+        shell(), embedded_test_server()->GetURL("/service_worker/empty.html"),
+        1);
+  }
+
+  ServiceWorkerVersion* CreateVersion() {
+    WorkerRunningStatusObserver observer(wrapper());
+    EXPECT_TRUE(NavigateToURL(
+        shell(), embedded_test_server()->GetURL(
+                     "/service_worker/create_service_worker.html")));
+    EXPECT_EQ("DONE", EvalJs(shell(), "register('maybe_offline_support.js');"));
+    observer.WaitUntilRunning();
+    return wrapper()->GetLiveVersion(observer.version_id());
+  }
+
+  void WaitForNoWork(ServiceWorkerVersion* version) {
+    // Set up a custom timeout for waiting for the service worker becomes an
+    // idle state on the renderer and it has no work on the browser. The default
+    // delay to become an idle is 30 seconds
+    // (kServiceWorkerDefaultIdleDelayInSeconds).
+    base::test::ScopedRunLoopTimeout specific_timeout(
+        FROM_HERE, base::TimeDelta::FromSeconds(35));
+    base::RunLoop run_loop;
+    NoWorkObserver observer(run_loop.QuitClosure());
+    version->AddObserver(&observer);
+    run_loop.Run();
+    version->RemoveObserver(&observer);
+  }
+
+  ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
+
+ protected:
+  scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
+};
+
+// Regression test for https://crbug.com/1145551.
+// This is the normal case that the lifetime of a fetch event is longer than
+// the response finishes. ServiceWorkerFetchDispatcher::HandleResponse() is
+// called first.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerFetchDispatcherBrowserTest, FetchEvent) {
+  StartServerAndNavigateToSetup();
+  ServiceWorkerVersion* version = CreateVersion();
+
+  FetchResult fetch_result;
+  base::RunLoop fetch_run_loop;
+  std::unique_ptr<ServiceWorkerFetchDispatcher> dispatcher =
+      CreateFetchDispatcher(
+          fetch_run_loop.QuitClosure(),
+          "/service_worker/empty.html?sleep_then_fetch&sleep=0", false, version,
+          &fetch_result);
+  dispatcher->Run();
+  fetch_run_loop.Run();
+
+  EXPECT_FALSE(version->HasNoWork());
+  EXPECT_EQ(kNetworkCompleted, fetch_result);
+
+  // Destruction of FetchDispatcher must not prevent FinishRequest() from being
+  // called.
+  dispatcher.reset();
+  WaitForNoWork(version);
+
+  EXPECT_TRUE(version->HasNoWork());
+}
+
+// Regression test for https://crbug.com/1145551.
+// This is the timeout case that the lifetime of a fetch event is shorter than
+// the response finishes. ServiceWorkerFetchDispatcher::OnFetchEventFinished is
+// called first.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerFetchDispatcherBrowserTest,
+                       FetchEventTimeout) {
+  StartServerAndNavigateToSetup();
+  ServiceWorkerVersion* version = CreateVersion();
+
+  FetchResult fetch_result;
+  base::RunLoop fetch_run_loop;
+  std::unique_ptr<ServiceWorkerFetchDispatcher> dispatcher =
+      CreateFetchDispatcher(
+          fetch_run_loop.QuitClosure(),
+          "/service_worker/empty.html?sleep_then_fetch&sleep=20000", true,
+          version, &fetch_result);
+  dispatcher->Run();
+  fetch_run_loop.Run();
+
+  EXPECT_FALSE(version->HasNoWork());
+  EXPECT_EQ(kTimeout, fetch_result);
+
+  // Destruction of FetchDispatcher must not prevent FinishRequest() from being
+  // called.
+  dispatcher.reset();
+  WaitForNoWork(version);
+
+  EXPECT_TRUE(version->HasNoWork());
+}
+
+}  // namespace content
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index aff9ead..7d40207 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -2289,6 +2289,9 @@
         context_->GetLiveRegistration(registration_id());
     if (registration)
       registration->OnNoWork(this);
+
+    for (auto& observer : observers_)
+      observer.OnNoWork(this);
   }
 }
 
diff --git a/content/browser/service_worker/service_worker_version.h b/content/browser/service_worker/service_worker_version.h
index 46aea41..cb5afe6 100644
--- a/content/browser/service_worker/service_worker_version.h
+++ b/content/browser/service_worker/service_worker_version.h
@@ -184,6 +184,7 @@
         const GURL& source_url) {}
     virtual void OnCachedMetadataUpdated(ServiceWorkerVersion* version,
                                          size_t size) {}
+    virtual void OnNoWork(ServiceWorkerVersion* version) {}
 
    protected:
     virtual ~Observer() {}
diff --git a/content/browser/webrtc/webrtc_image_capture_browsertest.cc b/content/browser/webrtc/webrtc_image_capture_browsertest.cc
index 7c56e681..ce7f83f 100644
--- a/content/browser/webrtc/webrtc_image_capture_browsertest.cc
+++ b/content/browser/webrtc/webrtc_image_capture_browsertest.cc
@@ -7,7 +7,6 @@
 #include "build/build_config.h"
 #include "content/browser/webrtc/webrtc_webcam_browsertest.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -100,10 +99,6 @@
 
     ASSERT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch(
         switches::kUseFakeDeviceForMediaStream));
-
-    // Enable Pan/Tilt for testing.
-    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
-                                    "MediaCapturePanTilt");
   }
 
   void SetUp() override {
diff --git a/content/browser/webui/web_ui_impl.h b/content/browser/webui/web_ui_impl.h
index 75e75044..7148949 100644
--- a/content/browser/webui/web_ui_impl.h
+++ b/content/browser/webui/web_ui_impl.h
@@ -121,15 +121,15 @@
   // The URL schemes that can be requested by this document.
   std::vector<std::string> requestable_schemes_;
 
-  // The WebUIMessageHandlers we own.
-  std::vector<std::unique_ptr<WebUIMessageHandler>> handlers_;
-
   // RenderFrameHost associated with |this|.
   RenderFrameHost* frame_host_;
 
   // Non-owning pointer to the WebContentsImpl this WebUI is associated with.
   WebContentsImpl* web_contents_;
 
+  // The WebUIMessageHandlers we own.
+  std::vector<std::unique_ptr<WebUIMessageHandler>> handlers_;
+
   // Notifies this WebUI about notifications in the main frame.
   std::unique_ptr<WebUIMainFrameObserver> web_contents_observer_;
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index ed1eba9..eda03a2 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -835,7 +835,7 @@
                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enable browser mediation API for federated identity interactions.
-const base::Feature kWebID{"kWebID", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kWebID{"WebID", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable experimental policy-controlled features and LAPIs
 const base::Feature kExperimentalProductivityFeatures{
@@ -843,7 +843,7 @@
 
 // When this feature is enabled, the Web OTP API will use the User Consent
 // API to retrieve SMSes from the Android device.
-const base::Feature kWebOtpBackend{"kWebOtpBackend",
+const base::Feature kWebOtpBackend{"WebOtpBackend",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
 
 // The JavaScript API for payments on the web.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d49b76f..becfd7b 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -387,6 +387,7 @@
     "//third_party/blink/public:blink",
     "//third_party/blink/public:test_support",
     "//third_party/blink/public/strings:strings_grit",
+    "//ui/gl:test_support",
   ]
 
   deps = [
@@ -459,7 +460,6 @@
     "//ui/events/blink",
     "//ui/gfx:test_support",
     "//ui/gl",
-    "//ui/gl:test_support",
     "//ui/native_theme",
     "//ui/resources",
     "//ui/shell_dialogs:shell_dialogs",
@@ -1134,6 +1134,7 @@
     "../browser/service_worker/service_worker_auth_browsertest.cc",
     "../browser/service_worker/service_worker_browsertest.cc",
     "../browser/service_worker/service_worker_clients_api_browsertest.cc",
+    "../browser/service_worker/service_worker_fetch_dispatcher_browsertest.cc",
     "../browser/service_worker/service_worker_file_upload_browsertest.cc",
     "../browser/service_worker/service_worker_no_best_effort_tasks_browsertest.cc",
     "../browser/service_worker/service_worker_offline_capability_check_browsertest.cc",
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index 40c2219..4159b0d1 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -353,11 +353,6 @@
 
 NavigationSimulatorImpl::~NavigationSimulatorImpl() {}
 
-void NavigationSimulatorImpl::SetIsPostWithId(int64_t post_id) {
-  post_id_ = post_id;
-  SetMethod("POST");
-}
-
 void NavigationSimulatorImpl::InitializeFromStartedRequest(
     NavigationRequest* request) {
   CHECK(request);
@@ -1311,8 +1306,9 @@
                                  : base::UnguessableToken::Create();
   params->post_id = post_id_;
 
-  if (intended_as_new_entry_.has_value())
-    params->intended_as_new_entry = intended_as_new_entry_.value();
+  params->intended_as_new_entry =
+      request_ ? request_->commit_params().intended_as_new_entry : false;
+  params->method = request_ ? request_->common_params().method : "GET";
 
   if (failed_navigation) {
     // Note: Error pages must commit in a unique origin. So it is left unset.
@@ -1320,7 +1316,6 @@
   } else {
     params->origin = origin_.value_or(url::Origin::Create(navigation_url_));
     params->redirects.push_back(navigation_url_);
-    params->method = request_ ? request_->common_params().method : "GET";
     params->http_status_code = 200;
     params->should_update_history = true;
   }
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index d50cad58..8a3ecb78 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -131,12 +131,6 @@
     should_replace_current_entry_ = should_replace_current_entry;
   }
 
-  // Manually force the value of intended_as_new_entry flag in DidCommit*Params
-  // to |intended_as_new_entry|.
-  void set_intended_as_new_entry(bool intended_as_new_entry) {
-    intended_as_new_entry_ = intended_as_new_entry;
-  }
-
   void set_http_connection_info(net::HttpResponseInfo::ConnectionInfo info) {
     http_connection_info_ = info;
   }
@@ -167,8 +161,6 @@
     impression_ = impression;
   }
 
-  void SetIsPostWithId(int64_t post_id);
-
  private:
   NavigationSimulatorImpl(const GURL& original_url,
                           bool browser_initiated,
@@ -315,7 +307,6 @@
   bool history_list_was_cleared_ = false;
   bool should_replace_current_entry_ = false;
   base::Optional<bool> did_create_new_entry_;
-  base::Optional<bool> intended_as_new_entry_;
   bool was_aborted_ = false;
 
   // These are used to sanity check the content/public/ API calls emitted as
diff --git a/extensions/browser/api/web_request/web_request_resource_type.cc b/extensions/browser/api/web_request/web_request_resource_type.cc
index aed40a7..4c49850 100644
--- a/extensions/browser/api/web_request/web_request_resource_type.cc
+++ b/extensions/browser/api/web_request/web_request_resource_type.cc
@@ -93,6 +93,7 @@
     case network::mojom::RequestDestination::kAudioWorklet:
     case network::mojom::RequestDestination::kManifest:
     case network::mojom::RequestDestination::kPaintWorklet:
+    case network::mojom::RequestDestination::kWebBundle:
       return WebRequestResourceType::OTHER;
   }
   NOTREACHED();
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
index 1602d7c..b43f3ce78 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
@@ -106,12 +106,13 @@
 
 void MimeHandlerViewEmbedder::DidFinishNavigation(
     content::NavigationHandle* handle) {
-  if (!render_frame_host_ ||
+  if (!handle->HasCommitted() ||
       frame_tree_node_id_ != handle->GetFrameTreeNodeId()) {
     return;
   }
   // We should've deleted the MimeHandlerViewEmbedder at this point if the frame
   // is sandboxed.
+  DCHECK(render_frame_host_);
   DCHECK(!render_frame_host_->IsSandboxed(
       network::mojom::WebSandboxFlags::kPlugins));
 }
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index bc08ab9..085d20c 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -2903,6 +2903,7 @@
   // view is taken, the snapshot will be a blank view. However, if the view's
   // parent is hidden but the view itself is not, the snapshot will not be a
   // blank view.
+  [self.tabStripSnapshot removeFromSuperview];
   self.tabStripSnapshot = [self.tabStripView screenshotForAnimation];
   self.tabStripSnapshot.translatesAutoresizingMaskIntoConstraints = NO;
   self.tabStripSnapshot.transform =
@@ -2923,12 +2924,11 @@
     // When the Fullscreen Provider is used, the web content extends up to the
     // top of the BVC view. It has a visible background and blocks the thumb
     // strip. Thus, when the view revealing process starts, the web content
-    // frame must be moved down. To prevent the actual web content from jumping,
-    // the content offset must be moved up by a corresponding amount.
+    // frame must be moved down and the content inset is decreased. To prevent
+    // the actual web content from jumping, the content offset must be moved up
+    // by a corresponding amount.
     if (self.currentWebState && ![self isNTPActiveForCurrentWebState] &&
-        ios::GetChromeBrowserProvider()
-            ->GetFullscreenProvider()
-            ->IsInitialized()) {
+        fullscreen::features::ShouldUseSmoothScrolling()) {
       CGFloat toolbarHeight = [self expandedTopToolbarHeight];
       CGRect webStateViewFrame = UIEdgeInsetsInsetRect(
           [self viewForWebState:self.currentWebState].frame,
@@ -2940,6 +2940,12 @@
       CGPoint scrollOffset = scrollProxy.contentOffset;
       scrollOffset.y += toolbarHeight;
       scrollProxy.contentOffset = scrollOffset;
+
+      // TODO(crbug.com/1155536): Inform FullscreenController about these
+      // changes and allow it to calculate the correct overall contentInset.
+      UIEdgeInsets contentInset = scrollProxy.contentInset;
+      contentInset.top -= toolbarHeight;
+      scrollProxy.contentInset = contentInset;
     }
   }
 }
@@ -3000,6 +3006,10 @@
 
       CRWWebViewScrollViewProxy* scrollProxy =
           self.currentWebState->GetWebViewProxy().scrollViewProxy;
+      UIEdgeInsets contentInset = scrollProxy.contentInset;
+      contentInset.top += toolbarHeight;
+      scrollProxy.contentInset = contentInset;
+
       CGPoint scrollOffset = scrollProxy.contentOffset;
       scrollOffset.y -= toolbarHeight;
       scrollProxy.contentOffset = scrollOffset;
diff --git a/ios/chrome/browser/ui/gestures/BUILD.gn b/ios/chrome/browser/ui/gestures/BUILD.gn
index 298734b..1a1e136 100644
--- a/ios/chrome/browser/ui/gestures/BUILD.gn
+++ b/ios/chrome/browser/ui/gestures/BUILD.gn
@@ -8,11 +8,16 @@
   sources = [
     "layout_switcher.h",
     "layout_switcher_provider.h",
+    "pan_handler_scroll_view.h",
+    "pan_handler_scroll_view.mm",
     "view_revealing_animatee.h",
     "view_revealing_vertical_pan_handler.h",
     "view_revealing_vertical_pan_handler.mm",
   ]
-  deps = [ "//base" ]
+  deps = [
+    "//base",
+    "//ios/web/public/ui",
+  ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
diff --git a/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.h b/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.h
new file mode 100644
index 0000000..69864be
--- /dev/null
+++ b/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.h
@@ -0,0 +1,28 @@
+// 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 IOS_CHROME_BROWSER_UI_GESTURES_PAN_HANDLER_SCROLL_VIEW_H_
+#define IOS_CHROME_BROWSER_UI_GESTURES_PAN_HANDLER_SCROLL_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+@class CRWWebViewScrollViewProxy;
+
+// This private class handles forwarding updates to these properties to an
+// underlying |UIScrollView| or |CRWWebViewScrollViewProxy|.
+@interface PanHandlerScrollView : NSObject
+
+@property(nonatomic) CGPoint contentOffset;
+@property(nonatomic, assign) UIEdgeInsets contentInset;
+@property(nonatomic, readonly) UIPanGestureRecognizer* panGestureRecognizer;
+@property(nonatomic, readonly, getter=isDecelerating) BOOL decelerating;
+@property(nonatomic, readonly, getter=isDragging) BOOL dragging;
+
+- (instancetype)initWithScrollView:(UIScrollView*)scrollView;
+- (instancetype)initWithWebViewScrollViewProxy:
+    (CRWWebViewScrollViewProxy*)scrollViewProxy;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_GESTURES_PAN_HANDLER_SCROLL_VIEW_H_
diff --git a/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.mm b/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.mm
new file mode 100644
index 0000000..d2a8116
--- /dev/null
+++ b/ios/chrome/browser/ui/gestures/pan_handler_scroll_view.mm
@@ -0,0 +1,72 @@
+// 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.
+
+#import "ios/chrome/browser/ui/gestures/pan_handler_scroll_view.h"
+
+#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface PanHandlerScrollView ()
+
+@property(nonatomic, strong) UIScrollView* scrollView;
+@property(nonatomic, strong) CRWWebViewScrollViewProxy* scrollViewProxy;
+
+@end
+
+@implementation PanHandlerScrollView
+
+- (instancetype)initWithScrollView:(UIScrollView*)scrollView {
+  if (self = [super init]) {
+    _scrollView = scrollView;
+  }
+  return self;
+}
+
+- (instancetype)initWithWebViewScrollViewProxy:
+    (CRWWebViewScrollViewProxy*)scrollViewProxy {
+  if (self = [super init]) {
+    _scrollViewProxy = scrollViewProxy;
+  }
+  return self;
+}
+
+- (CGPoint)contentOffset {
+  return (self.scrollView) ? self.scrollView.contentOffset
+                           : self.scrollViewProxy.contentOffset;
+}
+
+- (void)setContentOffset:(CGPoint)contentOffset {
+  self.scrollView.contentOffset = contentOffset;
+  self.scrollViewProxy.contentOffset = contentOffset;
+}
+
+- (UIEdgeInsets)contentInset {
+  return (self.scrollView) ? self.scrollView.contentInset
+                           : self.scrollViewProxy.contentInset;
+}
+
+- (void)setContentInset:(UIEdgeInsets)contentInset {
+  self.scrollView.contentInset = contentInset;
+  self.scrollViewProxy.contentInset = contentInset;
+}
+
+- (UIPanGestureRecognizer*)panGestureRecognizer {
+  return (self.scrollView) ? self.scrollView.panGestureRecognizer
+                           : self.scrollViewProxy.panGestureRecognizer;
+}
+
+- (BOOL)isDecelerating {
+  return (self.scrollView) ? self.scrollView.isDecelerating
+                           : self.scrollViewProxy.isDecelerating;
+}
+
+- (BOOL)isDragging {
+  return (self.scrollView) ? self.scrollView.isDragging
+                           : self.scrollViewProxy.isDragging;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h b/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h
index 24536a2..dc5343de3 100644
--- a/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h
+++ b/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h
@@ -9,6 +9,7 @@
 
 #import "ios/chrome/browser/ui/gestures/layout_switcher_provider.h"
 #import "ios/chrome/browser/ui/gestures/view_revealing_animatee.h"
+#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
 
 // Responsible for handling vertical pan gestures to reveal/hide a view behind
 // another.
@@ -21,7 +22,8 @@
 // TODO(crbug.com/1123512): Add support for going straight from a Hidden state
 // to a revealed state (and vice-versa) if the gesture's translation and
 // velocity are enough to trigger such transition.
-@interface ViewRevealingVerticalPanHandler : NSObject <UIScrollViewDelegate>
+@interface ViewRevealingVerticalPanHandler
+    : NSObject <CRWWebViewScrollViewProxyObserver, UIScrollViewDelegate>
 
 // |peekedHeight| is the height of the view when peeked (partially revealed).
 // |revealedCoverHeight| is the height of the cover view that remains visible
diff --git a/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.mm b/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.mm
index 21348f2..972c7ae 100644
--- a/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.mm
+++ b/ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.mm
@@ -4,9 +4,11 @@
 
 #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
 
+#include "base/logging.h"
 #import "base/notreached.h"
 #include "base/numerics/ranges.h"
 #import "ios/chrome/browser/ui/gestures/layout_switcher.h"
+#import "ios/chrome/browser/ui/gestures/pan_handler_scroll_view.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -366,27 +368,86 @@
 #pragma mark - UIScrollViewDelegate
 
 - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {
+  PanHandlerScrollView* view =
+      [[PanHandlerScrollView alloc] initWithScrollView:scrollView];
+  [self panHandlerScrollViewWillBeginDragging:view];
+}
+
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+  PanHandlerScrollView* view =
+      [[PanHandlerScrollView alloc] initWithScrollView:scrollView];
+  [self panHandlerScrollViewDidScroll:view];
+}
+
+- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
+                     withVelocity:(CGPoint)velocity
+              targetContentOffset:(inout CGPoint*)targetContentOffset {
+  PanHandlerScrollView* view =
+      [[PanHandlerScrollView alloc] initWithScrollView:scrollView];
+  [self panHandlerScrollViewWillEndDragging:view
+                               withVelocity:velocity
+                        targetContentOffset:targetContentOffset];
+}
+
+- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
+                  willDecelerate:(BOOL)decelerate {
+  // No-op.
+}
+
+#pragma mark - CRWWebViewScrollViewProxyObserver
+
+- (void)webViewScrollViewWillBeginDragging:
+    (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+  PanHandlerScrollView* view = [[PanHandlerScrollView alloc]
+      initWithWebViewScrollViewProxy:webViewScrollViewProxy];
+  [self panHandlerScrollViewWillBeginDragging:view];
+}
+
+- (void)webViewScrollViewDidScroll:
+    (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
+  PanHandlerScrollView* view = [[PanHandlerScrollView alloc]
+      initWithWebViewScrollViewProxy:webViewScrollViewProxy];
+  [self panHandlerScrollViewDidScroll:view];
+}
+
+- (void)webViewScrollViewWillEndDragging:
+            (CRWWebViewScrollViewProxy*)webViewScrollViewProxy
+                            withVelocity:(CGPoint)velocity
+                     targetContentOffset:(inout CGPoint*)targetContentOffset {
+  PanHandlerScrollView* view = [[PanHandlerScrollView alloc]
+      initWithWebViewScrollViewProxy:webViewScrollViewProxy];
+  [self panHandlerScrollViewWillEndDragging:view
+                               withVelocity:velocity
+                        targetContentOffset:targetContentOffset];
+}
+
+#pragma mark - UIScrollViewDelegate + CRWWebViewScrollViewProxyObserver
+
+- (void)panHandlerScrollViewWillBeginDragging:
+    (PanHandlerScrollView*)scrollView {
   switch (self.currentState) {
-    case ViewRevealState::Hidden:
+    case ViewRevealState::Hidden: {
       // The transition out of hidden state can only start if the scroll view
       // starts dragging from the top.
-      if (!self.animator.isRunning && scrollView.contentOffset.y != 0) {
+      CGFloat contentOffsetY =
+          scrollView.contentOffset.y + scrollView.contentInset.top;
+      if (contentOffsetY != 0) {
         return;
       }
       break;
+    }
     case ViewRevealState::Peeked:
       break;
     case ViewRevealState::Revealed:
       // The scroll views should be covered in Revealed state, so should not
       // be able to be scrolled.
       NOTREACHED();
-      break;
   }
   [self panGestureBegan];
   self.lastScrollOffset = scrollView.contentOffset;
 }
 
-- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+- (void)panHandlerScrollViewDidScroll:(PanHandlerScrollView*)scrollView {
   // These delegate methods are approximating the pan gesture handling from
   // above, so only change things if the user is actively scrolling.
   if (!scrollView.isDragging) {
@@ -404,15 +465,16 @@
   if (self.animator.fractionComplete > 0 &&
       self.animator.fractionComplete < 1) {
     CGPoint currentScrollOffset = scrollView.contentOffset;
-    currentScrollOffset.y = std::max(self.lastScrollOffset.y, 0.0);
+    currentScrollOffset.y = self.lastScrollOffset.y;
     scrollView.contentOffset = currentScrollOffset;
   }
   self.lastScrollOffset = scrollView.contentOffset;
 }
 
-- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView
-                     withVelocity:(CGPoint)velocity
-              targetContentOffset:(inout CGPoint*)targetContentOffset {
+- (void)panHandlerScrollViewWillEndDragging:(PanHandlerScrollView*)scrollView
+                               withVelocity:(CGPoint)velocity
+                        targetContentOffset:
+                            (inout CGPoint*)targetContentOffset {
   if (self.currentState == ViewRevealState::Hidden &&
       self.animator.state != UIViewAnimatingStateActive) {
     return;
@@ -429,9 +491,4 @@
   [self panGestureEndedWithTranslation:translationY velocity:velocityY];
 }
 
-- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView
-                  willDecelerate:(BOOL)decelerate {
-  // No-op.
-}
-
 @end
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
index ef480f30..a41c44b 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm
@@ -255,6 +255,13 @@
 }
 
 - (void)testCloseNTPWhenSwitching {
+// TODO(crbug.com/1156054): Test won't pass on iPad devices.
+#if !TARGET_IPHONE_SIMULATOR
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_SKIPPED(@"This test doesn't pass on iPad device.");
+  }
+#endif
+
   // Open the first page.
   GURL URL1 = self.testServer->GetURL(kPage1URL);
   [ChromeEarlGrey loadURL:URL1];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
index 86f191c..69bf944 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -148,6 +148,7 @@
 - (void)setIncognitoBrowser:(Browser*)incognitoBrowser {
   DCHECK(self.incognitoTabsMediator);
   self.incognitoTabsMediator.browser = incognitoBrowser;
+  self.thumbStripCoordinator.incognitoBrowser = incognitoBrowser;
 }
 
 - (void)stopChildCoordinatorsWithCompletion:(ProceduralBlock)completion {
@@ -400,6 +401,8 @@
     self.thumbStripCoordinator = [[ThumbStripCoordinator alloc]
         initWithBaseViewController:baseViewController
                            browser:self.browser];
+    self.thumbStripCoordinator.regularBrowser = _regularBrowser;
+    self.thumbStripCoordinator.incognitoBrowser = _incognitoBrowser;
     [self.thumbStripCoordinator start];
     self.thumbStripCoordinator.panHandler.layoutSwitcherProvider =
         baseViewController;
diff --git a/ios/chrome/browser/ui/thumb_strip/BUILD.gn b/ios/chrome/browser/ui/thumb_strip/BUILD.gn
index 66a03c989..931de5a 100644
--- a/ios/chrome/browser/ui/thumb_strip/BUILD.gn
+++ b/ios/chrome/browser/ui/thumb_strip/BUILD.gn
@@ -9,12 +9,18 @@
     "thumb_strip_attacher.h",
     "thumb_strip_coordinator.h",
     "thumb_strip_coordinator.mm",
+    "thumb_strip_mediator.h",
+    "thumb_strip_mediator.mm",
   ]
   deps = [
     "//base",
+    "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/gestures",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_ui_constants",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/web/public",
+    "//ios/web/public/ui",
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.h b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.h
index ee2c924..5eb44a6 100644
--- a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.h
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.h
@@ -9,7 +9,6 @@
 
 #import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
 
-@class ThumbStripCoordinator;
 @class ViewRevealingVerticalPanHandler;
 
 // Coordinator for the thumb strip, which is a 1-row horizontal display of tab
@@ -19,6 +18,13 @@
 // The thumb strip's pan gesture handler.
 @property(nonatomic, strong) ViewRevealingVerticalPanHandler* panHandler;
 
+// The regular browser used to observe scroll events to show/hide the thumb
+// strip.
+@property(nonatomic, assign) Browser* regularBrowser;
+// The incognito browser used to observe scroll events to show/hide the thumb
+// strip.
+@property(nonatomic, assign) Browser* incognitoBrowser;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_THUMB_STRIP_THUMB_STRIP_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
index 09c17a0..5879648f 100644
--- a/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.mm
@@ -4,8 +4,10 @@
 
 #import "ios/chrome/browser/ui/thumb_strip/thumb_strip_coordinator.h"
 
+#import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
+#import "ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -19,6 +21,8 @@
 
 @interface ThumbStripCoordinator ()
 
+@property(nonatomic, strong) ThumbStripMediator* mediator;
+
 @end
 
 @implementation ThumbStripCoordinator
@@ -31,10 +35,27 @@
       initWithPeekedHeight:kThumbStripHeight
        revealedCoverHeight:kBVCHeightTabGrid
             baseViewHeight:baseViewHeight];
+
+  self.mediator = [[ThumbStripMediator alloc] init];
+  if (self.regularBrowser) {
+    self.mediator.regularWebStateList = self.regularBrowser->GetWebStateList();
+  }
+  if (self.incognitoBrowser) {
+    self.mediator.incognitoWebStateList =
+        self.incognitoBrowser->GetWebStateList();
+  }
+  self.mediator.webViewScrollViewObserver = self.panHandler;
 }
 
 - (void)stop {
   self.panHandler = nil;
+  self.mediator = nil;
+}
+
+- (void)setIncognitoBrowser:(Browser*)incognitoBrowser {
+  _incognitoBrowser = incognitoBrowser;
+  self.mediator.incognitoWebStateList =
+      _incognitoBrowser ? _incognitoBrowser->GetWebStateList() : nullptr;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h
new file mode 100644
index 0000000..bd67907
--- /dev/null
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h
@@ -0,0 +1,29 @@
+// 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 IOS_CHROME_BROWSER_UI_THUMB_STRIP_THUMB_STRIP_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_THUMB_STRIP_THUMB_STRIP_MEDIATOR_H_
+
+#include <UIKit/UIKit.h>
+
+@protocol CRWWebViewScrollViewProxyObserver;
+class WebStateList;
+
+// Mediator for the thumb strip. Handles observing changes in the active web
+// state.
+@interface ThumbStripMediator : NSObject
+
+// The regular web state list to observe.
+@property(nonatomic, assign) WebStateList* regularWebStateList;
+// The incognito web state list to observe.
+@property(nonatomic, assign) WebStateList* incognitoWebStateList;
+
+// The observer to register/deregister as CRWWebViewScrollViewProxyObserver for
+// the active webstates in the given WebStateLists.
+@property(nonatomic, weak) id<CRWWebViewScrollViewProxyObserver>
+    webViewScrollViewObserver;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_THUMB_STRIP_THUMB_STRIP_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm
new file mode 100644
index 0000000..ccfea19
--- /dev/null
+++ b/ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.mm
@@ -0,0 +1,121 @@
+// 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.
+
+#import "ios/chrome/browser/ui/thumb_strip/thumb_strip_mediator.h"
+
+#include "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
+#import "ios/web/public/ui/crw_web_view_proxy.h"
+#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
+#import "ios/web/public/web_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface ThumbStripMediator () <WebStateListObserving> {
+  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
+}
+@end
+
+@implementation ThumbStripMediator
+
+- (instancetype)init {
+  if (self = [super init]) {
+    _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
+  }
+  return self;
+}
+
+- (void)dealloc {
+  if (_regularWebStateList) {
+    _regularWebStateList->RemoveObserver(_webStateListObserver.get());
+  }
+  if (_incognitoWebStateList) {
+    _incognitoWebStateList->RemoveObserver(_webStateListObserver.get());
+  }
+}
+
+- (void)setRegularWebStateList:(WebStateList*)regularWebStateList {
+  if (_regularWebStateList) {
+    _regularWebStateList->RemoveObserver(_webStateListObserver.get());
+    [self removeObserverFromWebState:_regularWebStateList->GetActiveWebState()];
+  }
+
+  _regularWebStateList = regularWebStateList;
+
+  if (_regularWebStateList) {
+    _regularWebStateList->AddObserver(_webStateListObserver.get());
+    [self addObserverToWebState:_regularWebStateList->GetActiveWebState()];
+  }
+}
+
+- (void)setIncognitoWebStateList:(WebStateList*)incognitoWebStateList {
+  if (_incognitoWebStateList) {
+    _incognitoWebStateList->RemoveObserver(_webStateListObserver.get());
+    [self
+        removeObserverFromWebState:_incognitoWebStateList->GetActiveWebState()];
+  }
+
+  _incognitoWebStateList = incognitoWebStateList;
+
+  if (_incognitoWebStateList) {
+    _incognitoWebStateList->AddObserver(_webStateListObserver.get());
+    [self addObserverToWebState:_incognitoWebStateList->GetActiveWebState()];
+  }
+}
+
+- (void)setWebViewScrollViewObserver:
+    (id<CRWWebViewScrollViewProxyObserver>)observer {
+  if (self.incognitoWebStateList) {
+    [self removeObserverFromWebState:self.incognitoWebStateList
+                                         ->GetActiveWebState()];
+  }
+  if (self.regularWebStateList) {
+    [self removeObserverFromWebState:self.regularWebStateList
+                                         ->GetActiveWebState()];
+  }
+
+  _webViewScrollViewObserver = observer;
+  if (self.incognitoWebStateList) {
+    [self
+        addObserverToWebState:self.incognitoWebStateList->GetActiveWebState()];
+  }
+  if (self.regularWebStateList) {
+    [self addObserverToWebState:self.regularWebStateList->GetActiveWebState()];
+  }
+}
+
+#pragma mark - Privates
+
+// Remove |self.webViewScrollViewObserver| from the given |webState|. |webState|
+// can be nullptr.
+- (void)removeObserverFromWebState:(web::WebState*)webState {
+  if (webState && self.webViewScrollViewObserver) {
+    [webState->GetWebViewProxy().scrollViewProxy
+        removeObserver:self.webViewScrollViewObserver];
+  }
+}
+
+// Add |self.webViewScrollViewObserver| to the given |webState|. |webState| can
+// be nullptr.
+- (void)addObserverToWebState:(web::WebState*)webState {
+  if (webState && self.webViewScrollViewObserver) {
+    [webState->GetWebViewProxy().scrollViewProxy
+        addObserver:self.webViewScrollViewObserver];
+  }
+}
+
+#pragma mark - WebStateListObserving
+
+- (void)webStateList:(WebStateList*)webStateList
+    didChangeActiveWebState:(web::WebState*)newWebState
+                oldWebState:(web::WebState*)oldWebState
+                    atIndex:(int)atIndex
+                     reason:(ActiveWebStateChangeReason)reason {
+  [self removeObserverFromWebState:oldWebState];
+  [self addObserverToWebState:newWebState];
+}
+
+@end
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index 79d08db..d9d21a93 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-1f29c01fcd8536a960c41e59b2f2895db5e0e934
\ No newline at end of file
+01ff106b761c5001e0a0e4613d44ccd469e9d59d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index 7c7e4a0..cf9c001 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-e87b0850b41c2c2320b941cebac1235f45eb5341
\ No newline at end of file
+8b5a6c206c8467611657cff6782e56e316d53024
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index 82fb39d..fef39a4 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-a33019a2d2e360621a84dabb62b84b913aa5a70e
\ No newline at end of file
+a724628e1fec659323e7ba2b1b1ff93b2aa732d5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index 203ab4412..1e2edc49 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-c1e238436617a03dc73071f2941b6ab9679afa67
\ No newline at end of file
+a0be55e12d60b4fff74c519dc832cf88398cea1a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index b4f453a1..6e9f39e 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-8881d8a684e5affd5f19aabe79b80ce0e2e64a25
\ No newline at end of file
+1134510d04adbaab848bd2e3a6edc2347e05e750
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 837d062..db0be50 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-02e75aaa90d4003bfd3a23bd4a0d87b45d841bb2
\ No newline at end of file
+883d8827475ada8564f242bfb0309efbb82f83e6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index 3c089fb..7977e78 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-8e5df053811ae750948aa3eb646e80a13913763d
\ No newline at end of file
+b799016b63915888acdbd187f5737b922ba9e466
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index 5e9e473d..c8aa553 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-9f628c8c45c31ca57dd1c93fb7a539f2a35418fe
\ No newline at end of file
+dfbcd0b052745759d92e63255e46ccae2e36ff67
\ No newline at end of file
diff --git a/media/gpu/test/image_quality_metrics.cc b/media/gpu/test/image_quality_metrics.cc
index e90025b..24dee621 100644
--- a/media/gpu/test/image_quality_metrics.cc
+++ b/media/gpu/test/image_quality_metrics.cc
@@ -5,6 +5,7 @@
 #include <math.h>
 #include <utility>
 
+#include "base/logging.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 #include "media/gpu/test/video_frame_helpers.h"
@@ -36,11 +37,9 @@
                          SimilarityMetrics mode) {
   ASSERT_TRUE_OR_RETURN(frame1->IsMappable() && frame2->IsMappable(),
                         std::numeric_limits<std::size_t>::max());
-  // TODO(crbug.com/1044509): Remove these assumptions.
-  ASSERT_TRUE_OR_RETURN(frame1->visible_rect() == frame2->visible_rect(),
-                        std::numeric_limits<std::size_t>::max());
-  ASSERT_TRUE_OR_RETURN(frame1->visible_rect().origin() == gfx::Point(0, 0),
-                        std::numeric_limits<std::size_t>::max());
+  ASSERT_TRUE_OR_RETURN(
+      frame1->visible_rect().size() == frame2->visible_rect().size(),
+      std::numeric_limits<std::size_t>::max());
   // These are used, only if frames are converted to I420, for keeping converted
   // frames alive until the end of function.
   scoped_refptr<VideoFrame> converted_frame1;
@@ -67,9 +66,10 @@
   ASSERT_TRUE_OR_RETURN(metric_func, std::numeric_limits<double>::max());
 
   return metric_func(
-      frame1->data(0), frame1->stride(0), frame1->data(1), frame1->stride(1),
-      frame1->data(2), frame1->stride(2), frame2->data(0), frame2->stride(0),
-      frame2->data(1), frame2->stride(1), frame2->data(2), frame2->stride(2),
+      frame1->visible_data(0), frame1->stride(0), frame1->visible_data(1),
+      frame1->stride(1), frame1->visible_data(2), frame1->stride(2),
+      frame2->visible_data(0), frame2->stride(0), frame2->visible_data(1),
+      frame2->stride(1), frame2->visible_data(2), frame2->stride(2),
       frame1->visible_rect().width(), frame1->visible_rect().height());
 }
 }  // namespace
@@ -77,24 +77,22 @@
 size_t CompareFramesWithErrorDiff(const VideoFrame& frame1,
                                   const VideoFrame& frame2,
                                   uint8_t tolerance) {
-  ASSERT_TRUE_OR_RETURN(frame1.format() == frame2.format(),
-                        std::numeric_limits<std::size_t>::max());
-  // TODO(crbug.com/1044509): Remove these assumption.
-  ASSERT_TRUE_OR_RETURN(frame1.visible_rect() == frame2.visible_rect(),
-                        std::numeric_limits<std::size_t>::max());
-  ASSERT_TRUE_OR_RETURN(frame1.visible_rect().origin() == gfx::Point(0, 0),
-                        std::numeric_limits<std::size_t>::max());
   ASSERT_TRUE_OR_RETURN(frame1.IsMappable() && frame2.IsMappable(),
                         std::numeric_limits<std::size_t>::max());
+  ASSERT_TRUE_OR_RETURN(frame1.format() == frame2.format(),
+                        std::numeric_limits<std::size_t>::max());
+  ASSERT_TRUE_OR_RETURN(
+      frame1.visible_rect().size() == frame2.visible_rect().size(),
+      std::numeric_limits<std::size_t>::max());
   size_t diff_cnt = 0;
 
   const VideoPixelFormat format = frame1.format();
   const size_t num_planes = VideoFrame::NumPlanes(format);
   const gfx::Size& visible_size = frame1.visible_rect().size();
   for (size_t i = 0; i < num_planes; ++i) {
-    const uint8_t* data1 = frame1.data(i);
+    const uint8_t* data1 = frame1.visible_data(i);
     const int stride1 = frame1.stride(i);
-    const uint8_t* data2 = frame2.data(i);
+    const uint8_t* data2 = frame2.visible_data(i);
     const int stride2 = frame2.stride(i);
     const size_t rows = VideoFrame::Rows(i, format, visible_size.height());
     const int row_bytes = VideoFrame::RowBytes(i, format, visible_size.width());
diff --git a/media/gpu/test/video.cc b/media/gpu/test/video.cc
index 0847f66b..55dcf93c 100644
--- a/media/gpu/test/video.cc
+++ b/media/gpu/test/video.cc
@@ -31,6 +31,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/libyuv/include/libyuv/convert.h"
 #include "third_party/libyuv/include/libyuv/planar_functions.h"
+#include "third_party/libyuv/include/libyuv/scale.h"
 
 namespace media {
 namespace test {
@@ -48,6 +49,79 @@
 
 Video::~Video() = default;
 
+std::unique_ptr<Video> Video::Expand(const gfx::Size& resolution,
+                                     const gfx::Rect& visible_rect) const {
+  LOG_ASSERT(IsLoaded()) << "The source video is not loaded";
+  LOG_ASSERT(pixel_format_ == VideoPixelFormat::PIXEL_FORMAT_NV12)
+      << "The pixel format of source video is not NV12";
+  LOG_ASSERT(visible_rect.size() == resolution_)
+      << "The resolution is different from the copied-into area of visible "
+      << "rectangle";
+  LOG_ASSERT(gfx::Rect(resolution).Contains(visible_rect))
+      << "The resolution doesn't contain visible rectangle";
+  LOG_ASSERT(visible_rect.x() % 2 == 0 && visible_rect.y() % 2 == 0)
+      << "An odd origin point is not supported";
+  auto new_video = std::make_unique<Video>(file_path_, metadata_file_path_);
+  new_video->frame_checksums_ = frame_checksums_;
+  new_video->thumbnail_checksums_ = thumbnail_checksums_;
+  new_video->profile_ = profile_;
+  new_video->codec_ = codec_;
+  new_video->frame_rate_ = frame_rate_;
+  new_video->num_frames_ = num_frames_;
+  new_video->num_fragments_ = num_fragments_;
+  new_video->resolution_ = resolution;
+  new_video->visible_rect_ = visible_rect;
+  new_video->pixel_format_ = pixel_format_;
+
+  const auto src_layout =
+      CreateVideoFrameLayout(PIXEL_FORMAT_NV12, resolution_, 1u /* alignment*/);
+  const auto dst_layout =
+      CreateVideoFrameLayout(PIXEL_FORMAT_NV12, resolution, 1u /* alignment*/);
+  const size_t src_frame_size =
+      src_layout->planes().back().offset + src_layout->planes().back().size;
+  const size_t dst_frame_size =
+      dst_layout->planes().back().offset + dst_layout->planes().back().size;
+  LOG_ASSERT(src_frame_size * num_frames_ == data_.size())
+      << "Unexpected data size";
+  std::vector<uint8_t> new_data(dst_frame_size * num_frames_);
+  auto compute_dst_visible_data_offset = [&dst_layout,
+                                          &visible_rect](size_t plane) {
+    const size_t stride = dst_layout->planes()[plane].stride;
+    const size_t bytes_per_pixel =
+        VideoFrame::BytesPerElement(dst_layout->format(), plane);
+    gfx::Point origin = visible_rect.origin();
+    LOG_ASSERT(dst_layout->format() == VideoPixelFormat::PIXEL_FORMAT_NV12)
+        << "The pixel format of destination video is not NV12";
+    if (plane == 1)
+      origin.SetPoint(origin.x() / 2, origin.y() / 2);
+    return stride * origin.y() + bytes_per_pixel * origin.x();
+  };
+  const size_t dst_y_visible_offset = compute_dst_visible_data_offset(0);
+  const size_t dst_uv_visible_offset = compute_dst_visible_data_offset(1);
+  for (size_t i = 0; i < num_frames_; i++) {
+    const uint8_t* src_plane = data_.data() + (i * src_frame_size);
+    uint8_t* const dst_plane = new_data.data() + (i * dst_frame_size);
+    uint8_t* const dst_y_plane_visible_data =
+        dst_plane + dst_layout->planes()[0].offset + dst_y_visible_offset;
+    uint8_t* const dst_uv_plane_visible_data =
+        dst_plane + dst_layout->planes()[1].offset + dst_uv_visible_offset;
+    // Copy the source buffer to the visible area of the destination buffer.
+    // libyuv::NV12Scale copies the source to the destination as-is when their
+    // resolutions are the same.
+    libyuv::NV12Scale(src_plane + src_layout->planes()[0].offset,
+                      src_layout->planes()[0].stride,
+                      src_plane + src_layout->planes()[1].offset,
+                      src_layout->planes()[1].stride, resolution_.width(),
+                      resolution_.height(), dst_y_plane_visible_data,
+                      dst_layout->planes()[0].stride, dst_uv_plane_visible_data,
+                      dst_layout->planes()[1].stride, visible_rect_.width(),
+                      visible_rect_.height(),
+                      libyuv::FilterMode::kFilterBilinear);
+  }
+  new_video->data_ = std::move(new_data);
+  return new_video;
+}
+
 std::unique_ptr<Video> Video::ConvertToNV12() const {
   LOG_ASSERT(IsLoaded()) << "The source video is not loaded";
   LOG_ASSERT(pixel_format_ == VideoPixelFormat::PIXEL_FORMAT_I420)
@@ -61,6 +135,7 @@
   new_video->num_frames_ = num_frames_;
   new_video->num_fragments_ = num_fragments_;
   new_video->resolution_ = resolution_;
+  new_video->visible_rect_ = visible_rect_;
   new_video->pixel_format_ = PIXEL_FORMAT_NV12;
 
   // Convert I420 To NV12.
@@ -239,6 +314,10 @@
   return resolution_;
 }
 
+gfx::Rect Video::VisibleRect() const {
+  return visible_rect_;
+}
+
 base::TimeDelta Video::GetDuration() const {
   return base::TimeDelta::FromSecondsD(static_cast<double>(num_frames_) /
                                        static_cast<double>(frame_rate_));
@@ -381,6 +460,9 @@
   }
   resolution_ = gfx::Size(static_cast<uint32_t>(width->GetInt()),
                           static_cast<uint32_t>(height->GetInt()));
+  // The default visible rectangle is (0, 0, |resolution_|). Expand() needs to
+  // be called to change the visible rectangle.
+  visible_rect_ = gfx::Rect(resolution_);
 
   // Find optional frame checksums. These are only required when using the frame
   // validator.
@@ -516,8 +598,10 @@
         VideoFrame::Rows(plane, frame->format(), resolution.height());
     const int row_bytes =
         VideoFrame::RowBytes(plane, frame->format(), resolution.width());
-    const size_t plane_size =
-        VideoFrame::PlaneSize(frame->format(), plane, resolution).GetArea();
+    // VideoFrame::PlaneSize() cannot be used because it computes the plane size
+    // with resolutions aligned by two while our test code works with a succinct
+    // buffer size.
+    const int plane_size = row_bytes * rows;
     const size_t current_pos = data->size();
     // TODO(dstaessens): Avoid resizing.
     data->resize(data->size() + plane_size);
diff --git a/media/gpu/test/video.h b/media/gpu/test/video.h
index f4118c4..4f0df62c 100644
--- a/media/gpu/test/video.h
+++ b/media/gpu/test/video.h
@@ -16,6 +16,7 @@
 #include "base/time/time.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_types.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace media {
@@ -36,6 +37,11 @@
 
   // Create a new Video instance by copying and converting |data_| to NV12.
   std::unique_ptr<Video> ConvertToNV12() const;
+  // Create a new Video instance by copying the content to |visible_rect_| area
+  // and expanding the resolution to |resolution_|. This is only supported for
+  // raw videos in the NV12 format.
+  std::unique_ptr<Video> Expand(const gfx::Size& resolution,
+                                const gfx::Rect& visible_rect) const;
 
   // Load the video file from disk. |max_frames| is the maximum number of
   // frames to be read from disk.
@@ -68,6 +74,8 @@
   uint32_t NumFragments() const;
   // Get the video resolution.
   gfx::Size Resolution() const;
+  // Get the video visible rectangle.
+  gfx::Rect VisibleRect() const;
   // Get the video duration.
   base::TimeDelta GetDuration() const;
 
@@ -145,6 +153,7 @@
   uint32_t num_frames_ = 0;
   uint32_t num_fragments_ = 0;
   gfx::Size resolution_;
+  gfx::Rect visible_rect_;
 
   DISALLOW_COPY_AND_ASSIGN(Video);
 };
diff --git a/media/gpu/test/video_encoder/video_encoder_client.cc b/media/gpu/test/video_encoder/video_encoder_client.cc
index deec69a7..888dd99 100644
--- a/media/gpu/test/video_encoder/video_encoder_client.cc
+++ b/media/gpu/test/video_encoder/video_encoder_client.cc
@@ -276,13 +276,13 @@
     coded_size = video_->Resolution();
   }
 
-  // TODO(crbug.com/1045825): Add support for videos with a visible rectangle
-  // not starting at (0,0).
   // Follow the behavior of the chrome capture stack; |natural_size| is the
   // dimension to be encoded.
   aligned_data_helper_ = std::make_unique<AlignedDataHelper>(
-      video_->Data(), video_->NumFrames(), video_->PixelFormat(), coded_size,
-      /*visible_rect=*/gfx::Rect(video_->Resolution()),
+      video_->Data(), video_->NumFrames(), video_->PixelFormat(),
+      /*src_coded_size=*/video_->Resolution(),
+      /*dst_coded_size=*/coded_size,
+      /*visible_rect=*/video_->VisibleRect(),
       /*natural_size=*/encoder_client_config_.output_resolution,
       encoder_client_config_.input_storage_type ==
               VideoEncodeAccelerator::Config::StorageType::kDmabuf
diff --git a/media/gpu/test/video_frame_helpers.cc b/media/gpu/test/video_frame_helpers.cc
index cd14e01..d71185f 100644
--- a/media/gpu/test/video_frame_helpers.cc
+++ b/media/gpu/test/video_frame_helpers.cc
@@ -269,10 +269,9 @@
 
 scoped_refptr<VideoFrame> ConvertVideoFrame(const VideoFrame* src_frame,
                                             VideoPixelFormat dst_pixel_format) {
-  gfx::Rect visible_rect = src_frame->visible_rect();
   auto dst_frame = VideoFrame::CreateFrame(
-      dst_pixel_format, visible_rect.size(), visible_rect, visible_rect.size(),
-      base::TimeDelta());
+      dst_pixel_format, src_frame->coded_size(), src_frame->visible_rect(),
+      src_frame->natural_size(), src_frame->timestamp());
   if (!dst_frame) {
     LOG(ERROR) << "Failed to convert video frame to " << dst_frame->format();
     return nullptr;
diff --git a/media/gpu/test/video_test_helpers.cc b/media/gpu/test/video_test_helpers.cc
index 37b621d..083c078 100644
--- a/media/gpu/test/video_test_helpers.cc
+++ b/media/gpu/test/video_test_helpers.cc
@@ -373,22 +373,25 @@
     const std::vector<uint8_t>& stream,
     uint32_t num_frames,
     VideoPixelFormat pixel_format,
-    const gfx::Size& coded_size,
-    const gfx::Rect& visible_area,
+    const gfx::Size& src_coded_size,
+    const gfx::Size& dst_coded_size,
+    const gfx::Rect& visible_rect,
     const gfx::Size& natural_size,
     VideoFrame::StorageType storage_type,
     gpu::GpuMemoryBufferFactory* const gpu_memory_buffer_factory)
     : num_frames_(num_frames),
       storage_type_(storage_type),
       gpu_memory_buffer_factory_(gpu_memory_buffer_factory),
-      visible_area_(visible_area),
+      visible_rect_(visible_rect),
       natural_size_(natural_size) {
   if (storage_type_ == VideoFrame::STORAGE_GPU_MEMORY_BUFFER) {
     LOG_ASSERT(gpu_memory_buffer_factory_ != nullptr);
-    InitializeGpuMemoryBufferFrames(stream, pixel_format, coded_size);
+    InitializeGpuMemoryBufferFrames(stream, pixel_format, src_coded_size,
+                                    dst_coded_size);
   } else {
     LOG_ASSERT(storage_type == VideoFrame::STORAGE_MOJO_SHARED_BUFFER);
-    InitializeAlignedMemoryFrames(stream, pixel_format, coded_size);
+    InitializeAlignedMemoryFrames(stream, pixel_format, src_coded_size,
+                                  dst_coded_size);
   }
   LOG_ASSERT(video_frame_data_.size() == num_frames_)
       << "Failed to initialize VideoFrames";
@@ -439,7 +442,7 @@
 
     gpu::MailboxHolder dummy_mailbox[media::VideoFrame::kMaxPlanes];
     return media::VideoFrame::WrapExternalGpuMemoryBuffer(
-        visible_area_, natural_size_, std::move(gpu_memory_buffer),
+        visible_rect_, natural_size_, std::move(gpu_memory_buffer),
         dummy_mailbox, base::DoNothing() /* mailbox_holder_release_cb_ */,
         base::TimeTicks::Now().since_origin());
   } else {
@@ -460,7 +463,7 @@
     const size_t video_frame_size =
         layout_->planes().back().offset + layout_->planes().back().size;
     return MojoSharedBufferVideoFrame::Create(
-        layout_->format(), layout_->coded_size(), visible_area_, natural_size_,
+        layout_->format(), layout_->coded_size(), visible_rect_, natural_size_,
         std::move(dup_handle), video_frame_size, offsets, strides,
         base::TimeTicks::Now().since_origin());
   }
@@ -469,7 +472,8 @@
 void AlignedDataHelper::InitializeAlignedMemoryFrames(
     const std::vector<uint8_t>& stream,
     const VideoPixelFormat pixel_format,
-    const gfx::Size& coded_size) {
+    const gfx::Size& src_coded_size,
+    const gfx::Size& dst_coded_size) {
   ASSERT_NE(pixel_format, PIXEL_FORMAT_UNKNOWN);
 
   // Calculate padding in bytes to be added after each plane required to keep
@@ -480,7 +484,7 @@
   // the VEA; each row of |src_strides| bytes in the original file needs to be
   // copied into a row of |strides_| bytes in the aligned file.
   size_t video_frame_size;
-  layout_ = GetAlignedVideoFrameLayout(pixel_format, coded_size,
+  layout_ = GetAlignedVideoFrameLayout(pixel_format, dst_coded_size,
                                        kPlatformBufferAlignment, nullptr,
                                        &video_frame_size);
   LOG_ASSERT(video_frame_size > 0UL);
@@ -488,7 +492,7 @@
   std::vector<size_t> src_plane_rows;
   size_t src_video_frame_size = 0;
   auto src_layout = GetAlignedVideoFrameLayout(
-      pixel_format, visible_area_.size(), 1u /* alignment */, &src_plane_rows,
+      pixel_format, src_coded_size, 1u /* alignment */, &src_plane_rows,
       &src_video_frame_size);
   LOG_ASSERT(stream.size() % src_video_frame_size == 0U)
       << "Stream byte size is not a product of calculated frame byte size";
@@ -520,17 +524,18 @@
 void AlignedDataHelper::InitializeGpuMemoryBufferFrames(
     const std::vector<uint8_t>& stream,
     const VideoPixelFormat pixel_format,
-    const gfx::Size& coded_size) {
+    const gfx::Size& src_coded_size,
+    const gfx::Size& dst_coded_size) {
 #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
   layout_ = GetPlatformVideoFrameLayout(
-      gpu_memory_buffer_factory_, pixel_format, visible_area_.size(),
+      gpu_memory_buffer_factory_, pixel_format, dst_coded_size,
       gfx::BufferUsage::SCANOUT_VEA_READ_CAMERA_AND_CPU_READ_WRITE);
   ASSERT_TRUE(layout_) << "Failed getting platform VideoFrameLayout";
 
   std::vector<size_t> src_plane_rows;
   size_t src_video_frame_size = 0;
   auto src_layout = GetAlignedVideoFrameLayout(
-      pixel_format, visible_area_.size(), 1u /* alignment */, &src_plane_rows,
+      pixel_format, src_coded_size, 1u /* alignment */, &src_plane_rows,
       &src_video_frame_size);
   LOG_ASSERT(stream.size() % src_video_frame_size == 0U)
       << "Stream byte size is not a product of calculated frame byte size";
@@ -539,7 +544,7 @@
   const uint8_t* src_frame_ptr = &stream[0];
   for (size_t i = 0; i < num_frames_; i++) {
     auto memory_frame =
-        VideoFrame::CreateFrame(pixel_format, coded_size, visible_area_,
+        VideoFrame::CreateFrame(pixel_format, dst_coded_size, visible_rect_,
                                 natural_size_, base::TimeDelta());
     LOG_ASSERT(!!memory_frame) << "Failed creating VideoFrame";
     for (size_t i = 0; i < num_planes; i++) {
@@ -652,7 +657,7 @@
   // changes made in crrev.com/c/2050895.
   scoped_refptr<const VideoFrame> video_frame =
       VideoFrame::WrapExternalYuvDataWithLayout(
-          *layout_, gfx::Rect(video_->Resolution()), video_->Resolution(),
+          *layout_, video_->VisibleRect(), video_->VisibleRect().size(),
           frame_data[0], frame_data[1], frame_data[2],
           base::TimeTicks::Now().since_origin());
   return video_frame;
diff --git a/media/gpu/test/video_test_helpers.h b/media/gpu/test/video_test_helpers.h
index d5d94586..e1b2df44 100644
--- a/media/gpu/test/video_test_helpers.h
+++ b/media/gpu/test/video_test_helpers.h
@@ -203,8 +203,9 @@
       const std::vector<uint8_t>& stream,
       uint32_t num_frames,
       VideoPixelFormat pixel_format,
-      const gfx::Size& coded_size,
-      const gfx::Rect& visible_area,
+      const gfx::Size& src_coded_size,
+      const gfx::Size& dst_coded_size,
+      const gfx::Rect& visible_rect,
       const gfx::Size& natural_size,
       VideoFrame::StorageType storage_type,
       gpu::GpuMemoryBufferFactory* const gpu_memory_buffer_factory);
@@ -234,12 +235,14 @@
   // kPlatformBufferAlignment.
   void InitializeAlignedMemoryFrames(const std::vector<uint8_t>& stream,
                                      const VideoPixelFormat pixel_format,
-                                     const gfx::Size& coded_size);
+                                     const gfx::Size& src_coded_size,
+                                     const gfx::Size& dst_coded_size);
   // Create GpuMemoryBuffer VideoFrame whose alignments is determined by
   // a GpuMemoryBuffer allocation backend (e.g. minigbm).
   void InitializeGpuMemoryBufferFrames(const std::vector<uint8_t>& stream,
                                        const VideoPixelFormat pixel_format,
-                                       const gfx::Size& coded_size);
+                                       const gfx::Size& src_coded_size,
+                                       const gfx::Size& dst_coded_size);
 
   // The index of VideoFrame to be read next.
   uint32_t frame_index_ = 0;
@@ -251,7 +254,7 @@
 
   // The layout of VideoFrames returned by GetNextFrame().
   base::Optional<VideoFrameLayout> layout_;
-  const gfx::Rect visible_area_;
+  const gfx::Rect visible_rect_;
   const gfx::Size natural_size_;
 
   // The frame data returned by GetNextFrame().
diff --git a/media/gpu/video_encode_accelerator_tests.cc b/media/gpu/video_encode_accelerator_tests.cc
index 947ce29e..f8ce169 100644
--- a/media/gpu/video_encode_accelerator_tests.cc
+++ b/media/gpu/video_encode_accelerator_tests.cc
@@ -476,6 +476,72 @@
   EXPECT_EQ(encoder->GetFrameReleasedCount(), nv12_video->NumFrames());
   EXPECT_TRUE(encoder->WaitForBitstreamProcessors());
 }
+
+// TODO(hiroh): Enable this test after the test dashboard becomes more green.
+// Encode VideoFrames with cropping the rectangle (0, 60, size).
+// Cropping is required in VideoEncodeAccelerator when zero-copy video
+// capture is enabled. One example is when 640x360 capture recording is
+// requested, a camera cannot produce the resolution and instead produce 640x480
+// frames with visible_rect=0, 60, 640x360.
+TEST_F(VideoEncoderTest,
+       DISABLED_FlushAtEndOfStream_NV12DmabufCroppingTopAndBottom) {
+  constexpr int kGrowHeight = 120;
+  const gfx::Size original_resolution = g_env->Video()->Resolution();
+  const gfx::Rect expanded_visible_rect(0, kGrowHeight / 2,
+                                        original_resolution.width(),
+                                        original_resolution.height());
+  const gfx::Size expanded_resolution(
+      original_resolution.width(), original_resolution.height() + kGrowHeight);
+  auto nv12_video = g_env->Video()->ConvertToNV12();
+  ASSERT_TRUE(nv12_video);
+  auto nv12_expanded_video =
+      nv12_video->Expand(expanded_resolution, expanded_visible_rect);
+  ASSERT_TRUE(nv12_expanded_video);
+  nv12_video.reset();
+  VideoEncoderClientConfig config(nv12_expanded_video.get(), g_env->Profile(),
+                                  g_env->NumTemporalLayers(), g_env->Bitrate());
+  config.output_resolution = original_resolution;
+  config.input_storage_type =
+      VideoEncodeAccelerator::Config::StorageType::kDmabuf;
+
+  auto encoder = CreateVideoEncoder(nv12_expanded_video.get(), config);
+  encoder->Encode();
+  EXPECT_TRUE(encoder->WaitForFlushDone());
+  EXPECT_EQ(encoder->GetFlushDoneCount(), 1u);
+  EXPECT_EQ(encoder->GetFrameReleasedCount(), nv12_expanded_video->NumFrames());
+  EXPECT_TRUE(encoder->WaitForBitstreamProcessors());
+}
+
+// TODO(hiroh): Enable this test after the test dashboard becomes more green.
+// Encode VideoFrames with cropping the rectangle (60, 0, size).
+TEST_F(VideoEncoderTest,
+       DISABLED_FlushAtEndOfStream_NV12DmabufCroppingRightAndLeft) {
+  constexpr int kGrowWidth = 120;
+  const gfx::Size original_resolution = g_env->Video()->Resolution();
+  const gfx::Rect expanded_visible_rect(kGrowWidth / 2, 0,
+                                        original_resolution.width(),
+                                        original_resolution.height());
+  const gfx::Size expanded_resolution(original_resolution.width() + kGrowWidth,
+                                      original_resolution.height());
+  auto nv12_video = g_env->Video()->ConvertToNV12();
+  ASSERT_TRUE(nv12_video);
+  auto nv12_expanded_video =
+      nv12_video->Expand(expanded_resolution, expanded_visible_rect);
+  ASSERT_TRUE(nv12_expanded_video);
+  nv12_video.reset();
+  VideoEncoderClientConfig config(nv12_expanded_video.get(), g_env->Profile(),
+                                  g_env->NumTemporalLayers(), g_env->Bitrate());
+  config.output_resolution = original_resolution;
+  config.input_storage_type =
+      VideoEncodeAccelerator::Config::StorageType::kDmabuf;
+
+  auto encoder = CreateVideoEncoder(nv12_expanded_video.get(), config);
+  encoder->Encode();
+  EXPECT_TRUE(encoder->WaitForFlushDone());
+  EXPECT_EQ(encoder->GetFlushDoneCount(), 1u);
+  EXPECT_EQ(encoder->GetFrameReleasedCount(), nv12_expanded_video->NumFrames());
+  EXPECT_TRUE(encoder->WaitForBitstreamProcessors());
+}
 }  // namespace test
 }  // namespace media
 
diff --git a/services/network/public/cpp/request_destination.cc b/services/network/public/cpp/request_destination.cc
index 38884cc..1b7e991 100644
--- a/services/network/public/cpp/request_destination.cc
+++ b/services/network/public/cpp/request_destination.cc
@@ -47,6 +47,8 @@
       return "track";
     case network::mojom::RequestDestination::kVideo:
       return "video";
+    case network::mojom::RequestDestination::kWebBundle:
+      return "webbundle";
     case network::mojom::RequestDestination::kWorker:
       return "worker";
     case network::mojom::RequestDestination::kXslt:
@@ -56,4 +58,4 @@
   return "empty";
 }
 
-}  // namespace network
\ No newline at end of file
+}  // namespace network
diff --git a/services/network/public/cpp/resource_request.cc b/services/network/public/cpp/resource_request.cc
index 26bc215..7228cd0 100644
--- a/services/network/public/cpp/resource_request.cc
+++ b/services/network/public/cpp/resource_request.cc
@@ -7,6 +7,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/load_flags.h"
 #include "services/network/public/mojom/cookie_access_observer.mojom.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom.h"
 
 namespace network {
 
@@ -34,6 +35,13 @@
   return (!lhs && !rhs) || (lhs && rhs && lhs->EqualsForTesting(*rhs));
 }
 
+bool OptionalWebBundleTokenParamsEqualsForTesting(  // IN-TEST
+    const base::Optional<ResourceRequest::WebBundleTokenParams>& lhs,
+    const base::Optional<ResourceRequest::WebBundleTokenParams>& rhs) {
+  return (!lhs && !rhs) ||
+         (lhs && rhs && lhs->EqualsForTesting(*rhs));  // IN-TEST
+}
+
 }  // namespace
 
 ResourceRequest::TrustedParams::TrustedParams() = default;
@@ -63,6 +71,47 @@
          client_security_state == trusted_params.client_security_state;
 }
 
+ResourceRequest::WebBundleTokenParams::WebBundleTokenParams() = default;
+ResourceRequest::WebBundleTokenParams::~WebBundleTokenParams() = default;
+
+ResourceRequest::WebBundleTokenParams::WebBundleTokenParams(
+    const WebBundleTokenParams& other) {
+  *this = other;
+}
+
+ResourceRequest::WebBundleTokenParams&
+ResourceRequest::WebBundleTokenParams::operator=(
+    const WebBundleTokenParams& other) {
+  token = other.token;
+  handle = other.CloneHandle();
+  return *this;
+}
+
+ResourceRequest::WebBundleTokenParams::WebBundleTokenParams(
+    const base::UnguessableToken& token,
+    mojo::PendingRemote<mojom::WebBundleHandle> handle)
+    : token(token), handle(std::move(handle)) {}
+
+bool ResourceRequest::WebBundleTokenParams::EqualsForTesting(
+    const WebBundleTokenParams& other) const {
+  return token == other.token &&
+         ((handle && other.handle) || (!handle && !other.handle));
+}
+
+mojo::PendingRemote<mojom::WebBundleHandle>
+ResourceRequest::WebBundleTokenParams::CloneHandle() const {
+  if (!handle)
+    return mojo::NullRemote();
+  mojo::Remote<network::mojom::WebBundleHandle> remote(std::move(
+      const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>(
+          handle)));
+  mojo::PendingRemote<network::mojom::WebBundleHandle> new_remote;
+  remote->Clone(new_remote.InitWithNewPipeAndPassReceiver());
+  const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>(handle) =
+      remote.Unbind();
+  return new_remote;
+}
+
 ResourceRequest::ResourceRequest() {}
 ResourceRequest::ResourceRequest(const ResourceRequest& request) = default;
 ResourceRequest::~ResourceRequest() {}
@@ -122,7 +171,9 @@
          recursive_prefetch_token == request.recursive_prefetch_token &&
          OptionalTrustedParamsEqualsForTesting(trusted_params,
                                                request.trusted_params) &&
-         trust_token_params == request.trust_token_params;
+         trust_token_params == request.trust_token_params &&
+         OptionalWebBundleTokenParamsEqualsForTesting(  // IN-TEST
+             web_bundle_token_params, request.web_bundle_token_params);
 }
 
 bool ResourceRequest::SendsCookies() const {
diff --git a/services/network/public/cpp/resource_request.h b/services/network/public/cpp/resource_request.h
index 55f87653..c2574b1 100644
--- a/services/network/public/cpp/resource_request.h
+++ b/services/network/public/cpp/resource_request.h
@@ -26,6 +26,7 @@
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -57,6 +58,32 @@
     mojom::ClientSecurityStatePtr client_security_state;
   };
 
+  // Typemapped to network.mojom.WebBundleTokenParams, see comments there
+  // for details of each field.
+  struct COMPONENT_EXPORT(NETWORK_CPP_BASE) WebBundleTokenParams {
+    WebBundleTokenParams();
+    ~WebBundleTokenParams();
+    // Define a non-default copy-constructor because:
+    // 1. network::ResourceRequest has a requirement that all of
+    //    the members be trivially copyable.
+    // 2. mojo::PendingRemote is non-copyable.
+    WebBundleTokenParams(const WebBundleTokenParams& params);
+    WebBundleTokenParams& operator=(const WebBundleTokenParams& other);
+
+    WebBundleTokenParams(const base::UnguessableToken& token,
+                         mojo::PendingRemote<mojom::WebBundleHandle> handle);
+
+    // For testing. Regarding the equality of |handle|, |this| equals |other| if
+    // both |handle| exists, or neither exists, because we cannot test the
+    // equality of two mojo handles.
+    bool EqualsForTesting(const WebBundleTokenParams& other) const;
+
+    mojo::PendingRemote<mojom::WebBundleHandle> CloneHandle() const;
+
+    base::UnguessableToken token;
+    mojo::PendingRemote<mojom::WebBundleHandle> handle;
+  };
+
   ResourceRequest();
   ResourceRequest(const ResourceRequest& request);
   ~ResourceRequest();
@@ -125,6 +152,7 @@
   // field trivially copyable; see OptionalTrustTokenParams's definition for
   // more context.
   OptionalTrustTokenParams trust_token_params;
+  base::Optional<WebBundleTokenParams> web_bundle_token_params;
 };
 
 // This does not accept |kDefault| referrer policy.
diff --git a/services/network/public/cpp/url_request_mojom_traits.cc b/services/network/public/cpp/url_request_mojom_traits.cc
index afd04ed..6aeb457 100644
--- a/services/network/public/cpp/url_request_mojom_traits.cc
+++ b/services/network/public/cpp/url_request_mojom_traits.cc
@@ -20,6 +20,7 @@
 #include "services/network/public/mojom/cookie_access_observer.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom-shared.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom.h"
 #include "url/mojom/origin_mojom_traits.h"
 #include "url/mojom/url_gurl_mojom_traits.h"
 
@@ -162,6 +163,18 @@
   return true;
 }
 
+bool StructTraits<network::mojom::WebBundleTokenParamsDataView,
+                  network::ResourceRequest::WebBundleTokenParams>::
+    Read(network::mojom::WebBundleTokenParamsDataView data,
+         network::ResourceRequest::WebBundleTokenParams* out) {
+  if (!data.ReadToken(&out->token)) {
+    return false;
+  }
+  out->handle = data.TakeWebBundleHandle<
+      mojo::PendingRemote<network::mojom::WebBundleHandle>>();
+  return true;
+}
+
 bool StructTraits<
     network::mojom::URLRequestDataView,
     network::ResourceRequest>::Read(network::mojom::URLRequestDataView data,
@@ -203,7 +216,8 @@
       !data.ReadFetchWindowId(&out->fetch_window_id) ||
       !data.ReadDevtoolsRequestId(&out->devtools_request_id) ||
       !data.ReadDevtoolsStackId(&out->devtools_stack_id) ||
-      !data.ReadRecursivePrefetchToken(&out->recursive_prefetch_token)) {
+      !data.ReadRecursivePrefetchToken(&out->recursive_prefetch_token) ||
+      !data.ReadWebBundleTokenParams(&out->web_bundle_token_params)) {
     // Note that data.ReadTrustTokenParams is temporarily handled below.
     return false;
   }
diff --git a/services/network/public/cpp/url_request_mojom_traits.h b/services/network/public/cpp/url_request_mojom_traits.h
index a07ac04f..51075e7 100644
--- a/services/network/public/cpp/url_request_mojom_traits.h
+++ b/services/network/public/cpp/url_request_mojom_traits.h
@@ -30,6 +30,7 @@
 #include "services/network/public/mojom/data_pipe_getter.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom-shared.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom-shared.h"
 #include "url/mojom/url_gurl_mojom_traits.h"
 
 namespace mojo {
@@ -87,6 +88,27 @@
 
 template <>
 struct COMPONENT_EXPORT(NETWORK_CPP_BASE)
+    StructTraits<network::mojom::WebBundleTokenParamsDataView,
+                 network::ResourceRequest::WebBundleTokenParams> {
+  static const base::UnguessableToken& token(
+      const network::ResourceRequest::WebBundleTokenParams& params) {
+    return params.token;
+  }
+  static mojo::PendingRemote<network::mojom::WebBundleHandle> web_bundle_handle(
+      const network::ResourceRequest::WebBundleTokenParams& params) {
+    if (!params.handle)
+      return mojo::NullRemote();
+    return std::move(
+        const_cast<network::ResourceRequest::WebBundleTokenParams&>(params)
+            .handle);
+  }
+
+  static bool Read(network::mojom::WebBundleTokenParamsDataView data,
+                   network::ResourceRequest::WebBundleTokenParams* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(NETWORK_CPP_BASE)
     StructTraits<network::mojom::URLRequestDataView, network::ResourceRequest> {
   static const std::string& method(const network::ResourceRequest& request) {
     return request.method;
@@ -264,6 +286,10 @@
       const network::ResourceRequest& request) {
     return request.trust_token_params.as_ptr();
   }
+  static const base::Optional<network::ResourceRequest::WebBundleTokenParams>&
+  web_bundle_token_params(const network::ResourceRequest& request) {
+    return request.web_bundle_token_params;
+  }
 
   static bool Read(network::mojom::URLRequestDataView data,
                    network::ResourceRequest* out);
diff --git a/services/network/public/cpp/url_request_mojom_traits_unittest.cc b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
index b138f57d..f44e8d9 100644
--- a/services/network/public/cpp/url_request_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/public/cpp/url_request_mojom_traits.h"
 
+#include "base/optional.h"
 #include "base/test/gtest_util.h"
 #include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
@@ -101,6 +102,10 @@
       mojom::TrustTokenSignRequestData::kInclude;
   original.trust_token_params->additional_signed_headers.push_back(
       "some_header");
+  original.web_bundle_token_params =
+      base::make_optional(ResourceRequest::WebBundleTokenParams(
+          base::UnguessableToken::Create(),
+          mojo::PendingRemote<network::mojom::WebBundleHandle>()));
 
   network::ResourceRequest copied;
   EXPECT_TRUE(
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index f49d2d4..c56f515 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -585,6 +585,10 @@
           cpp = "::network::ResourceRequest::TrustedParams"
         },
         {
+          mojom = "network.mojom.WebBundleTokenParams"
+          cpp = "::network::ResourceRequest::WebBundleTokenParams"
+        },
+        {
           mojom = "network.mojom.URLRequest"
           cpp = "::network::ResourceRequest"
         },
diff --git a/services/network/public/mojom/fetch_api.mojom b/services/network/public/mojom/fetch_api.mojom
index 8b3f2f1..50304b9b 100644
--- a/services/network/public/mojom/fetch_api.mojom
+++ b/services/network/public/mojom/fetch_api.mojom
@@ -47,6 +47,14 @@
   kStyle,
   kTrack,
   kVideo,
+  // kWebBundle represents a request for a WebBundle. A <link> element whose
+  // rel is "webbundle" uses this destination.
+  //
+  // e.g. <link rel=webbundle href="example.com/foo.wbn" resources="...">
+  //
+  // Fetch specifiction does not define this destination yet.
+  // Tracking issue: https://github.com/whatwg/fetch/issues/1120
+  kWebBundle,
   kWorker,
   kXslt,
 };
diff --git a/services/network/public/mojom/url_loader.mojom b/services/network/public/mojom/url_loader.mojom
index 24efcd6e..a38d9e98 100644
--- a/services/network/public/mojom/url_loader.mojom
+++ b/services/network/public/mojom/url_loader.mojom
@@ -20,6 +20,7 @@
 import "services/network/public/mojom/site_for_cookies.mojom";
 import "services/network/public/mojom/trust_tokens.mojom";
 import "services/network/public/mojom/url_response_head.mojom";
+import "services/network/public/mojom/web_bundle_handle.mojom";
 import "url/mojom/origin.mojom";
 import "url/mojom/url.mojom";
 
@@ -97,6 +98,17 @@
   ClientSecurityState? client_security_state;
 };
 
+// Options that may only be set on URLRequests which are related to WebBundle.
+struct WebBundleTokenParams {
+  // Unique token to identify a WebBundle.
+  mojo_base.mojom.UnguessableToken token;
+  // Handle for the WebBundle-related communication between the network process
+  // and the renderer. This is also used as a 'keep-alive' handle. We clean up
+  // the WebBundle data in the network process when the renderer-side endpoint
+  // is deleted.
+  pending_remote<WebBundleHandle>? web_bundle_handle;
+};
+
 // Typemapped to network::ResourceRequest.
 struct URLRequest {
   // The request method: GET, POST, etc.
@@ -402,6 +414,10 @@
   // and the request has set the trustToken Fetch parameter, denoting that it
   // wishes to execute a Trust Tokens protocol operation.
   TrustTokenParams? trust_token_params;
+
+  // Set for WebBundle related requests. See the comment of WebBundleTokenParams
+  // for details.
+  WebBundleTokenParams? web_bundle_token_params;
 };
 
 // URLRequestBody represents body (i.e. upload data) of a HTTP request.
diff --git a/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc b/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
index cfd1336..7215c2d 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_tracing_backend.cc
@@ -511,6 +511,12 @@
     NOTREACHED();
   }
 
+  void SaveTraceForBugreport(SaveTraceForBugreportCallback) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    // Not implemented yet.
+    NOTREACHED();
+  }
+
   // tracing::mojom::TracingSessionClient implementation:
   void OnTracingEnabled() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 6d2462ce..2707bad 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -8632,10 +8632,348 @@
     ]
   },
   "Linux FYI Experimental Release (Intel HD 630)": {
+    "gtest_tests": [
+      {
+        "args": [
+          "angle_end2end_tests",
+          "--gtest_filter=-*Vulkan_SwiftShader*",
+          "--bot-mode",
+          "--max-processes=4"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "angle_end2end_tests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_end2end_tests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "angle_unittests"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "angle_white_box_tests",
+          "--bot-mode"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_white_box_tests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_white_box_tests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "--enable-gpu",
+          "--test-launcher-bot-mode",
+          "--test-launcher-jobs=1",
+          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*",
+          "--no-xvfb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "tab_capture_end2end_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=passthrough",
+          "--use-gl=angle",
+          "--use-gpu-in-tests",
+          "--no-xvfb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gl_tests_passthrough",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=validating",
+          "--use-gpu-in-tests",
+          "--no-xvfb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gl_tests_validating",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests",
+          "--no-xvfb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gles2_conform_test",
+        "test_id_prefix": "ninja://gpu/gles2_conform_support:gles2_conform_test/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "swiftshader_unittests",
+        "test_id_prefix": "ninja://third_party/swiftshader/tests/GLESUnitTests:swiftshader_unittests/"
+      }
+    ],
     "isolated_scripts": [
       {
         "args": [
-          "noop_sleep",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_angle_revision}"
+        ],
+        "isolate_name": "angle_restricted_trace_gold_tests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "angle_restricted_trace_gold_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/angle/src/tests/restricted_traces:angle_restricted_trace_gold_tests/"
+      },
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_passthrough_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "depth_capture",
           "--show-stdout",
           "--browser=release",
           "--passthrough",
@@ -8647,15 +8985,18 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "noop_sleep_tests",
+        "name": "depth_capture_tests",
+        "resultdb": {
+          "enable": true
+        },
         "should_retry_with_patch": false,
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:5912-19.0.2",
-              "os": "Ubuntu-19.04",
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -8664,6 +9005,548 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
+          "--expected-vendor-id",
+          "8086",
+          "--expected-device-id",
+          "5912"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "info_collection_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_pixel_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_pixel_validating_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_skia_gold_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_skia_gold_validating_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_passthrough_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating",
+          "--dont-restore-color-profile-after-test"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "trace_test",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_gl_passthrough_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_gl_passthrough_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
       }
     ]
   },
@@ -12507,8 +13390,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12533,8 +13416,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12559,8 +13442,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12573,6 +13456,35 @@
       },
       {
         "args": [
+          "--enable-gpu",
+          "--test-launcher-bot-mode",
+          "--test-launcher-jobs=1",
+          "--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*",
+          "--no-xvfb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "tab_capture_end2end_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 14400,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
           "--use-cmd-decoder=passthrough",
           "--use-gl=angle",
           "--use-gpu-in-tests",
@@ -12588,8 +13500,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12615,8 +13527,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12640,8 +13552,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12664,8 +13576,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12685,8 +13597,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12720,8 +13632,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12754,8 +13666,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12789,8 +13701,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12824,8 +13736,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12859,8 +13771,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12894,8 +13806,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12933,8 +13845,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -12977,8 +13889,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13021,8 +13933,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13065,8 +13977,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13109,8 +14021,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13145,8 +14057,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13181,8 +14093,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13216,8 +14128,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13253,8 +14165,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13291,8 +14203,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13328,8 +14240,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
@@ -13365,8 +14277,8 @@
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:3e92-19.0.8|8086:3e92-20.0.8",
-              "os": "Ubuntu-19.04|Ubuntu-18.04.5",
+              "gpu": "8086:3e92-20.0.8",
+              "os": "Ubuntu-18.04.5",
               "pool": "chromium.tests.gpu"
             }
           ],
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 7c15eab5..63599b1 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -447,8 +447,8 @@
     # Similar to stable, but with a newer Mesa version.
     'swarming': {
       'dimensions': {
-        'gpu': '8086:5912-19.0.2',
-        'os': 'Ubuntu-19.04',
+        'gpu': '8086:5912-20.0.8',
+        'os': 'Ubuntu-18.04.5',
         'pool': 'chromium.tests.gpu',
       },
     },
@@ -465,8 +465,8 @@
   'linux_intel_uhd_630_stable': {
     'swarming': {
       'dimensions': {
-        'gpu': '8086:3e92-19.0.8|8086:3e92-20.0.8',
-        'os': 'Ubuntu-19.04|Ubuntu-18.04.5',
+        'gpu': '8086:3e92-20.0.8',
+        'os': 'Ubuntu-18.04.5',
         'pool': 'chromium.tests.gpu',
       },
       # Same long expiration as win10_intel_uhd_630_stable due to capacity.
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 571beeb6..6a38251 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2569,7 +2569,6 @@
       'Win7 FYI Debug (AMD)',
       # Disabled due to dbus crashes crbug.com/927465
       'Linux FYI Release (Intel HD 630)',
-      'Linux FYI Release (Intel UHD 630)',
       'Linux FYI Release (NVIDIA)',
       'Linux FYI SkiaRenderer Vulkan (Intel HD 630)',
       'Linux FYI SkiaRenderer Vulkan (NVIDIA)',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index f1e0a0e..edfb0b2e 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3861,11 +3861,10 @@
           'limited_capacity_bot',
           'linux_intel_hd_630_experimental',
         ],
-        # Currently the experimental driver is identical to the stable
-        # driver. If it's upgraded, change these test_suites to be the same as
-        # 'Linux FYI Release (Intel HD 630)'.
         'test_suites': {
-          'gpu_telemetry_tests': 'gpu_noop_sleep_telemetry_test',
+          'gtest_tests': 'gpu_fyi_linux_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_linux_release_telemetry_tests',
+          'isolated_scripts': 'gpu_angle_restricted_trace_gold_isolated_scripts',
         },
       },
       'Linux FYI Experimental Release (NVIDIA)': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 47a2a136..0b5cba7 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2304,25 +2304,6 @@
             ]
         }
     ],
-    "DesktopNtpShoppingTasksModule": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NtpModules",
-                        "NtpShoppingTasksModule"
-                    ]
-                }
-            ]
-        }
-    ],
     "DesktopTabGroupsCollapse": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 082d0b5..ca2e7dff 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -972,7 +972,6 @@
   kPointerEventSetCapture = 1431,
   kPointerEventDispatch = 1432,
   kMIDIMessageEventReceivedTime = 1433,
-  kSummaryElementWithDisplayBlockAuthorRule = 1434,
   kV8MediaStream_Active_AttributeGetter = 1435,
   kBeforeInstallPromptEvent = 1436,
   kBeforeInstallPromptEventUserChoice = 1437,
@@ -2662,7 +2661,6 @@
   kContentVisibilityHiddenMatchable = 3334,
   kInlineOverflowAutoWithInlineEndPadding = 3335,
   kInlineOverflowScrollWithInlineEndPadding = 3336,
-  kCSSSelectorPseudoWebKitDetailsMarker = 3337,
   kSerialPortGetInfo = 3338,
   // The above items are available in M85 branch.
 
@@ -3064,6 +3062,8 @@
   kCrossOriginStrictNosniffWouldBlock = 3740,
   kCSSSelectorPseudoDir = 3741,
   kCrossOriginSubframeWithoutEmbeddingControl = 3742,
+  kReadableStreamWithByteSource = 3743,
+  kReadableStreamBYOBReader = 3744,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_url_request.h b/third_party/blink/public/platform/web_url_request.h
index fc06765..1742587e 100644
--- a/third_party/blink/public/platform/web_url_request.h
+++ b/third_party/blink/public/platform/web_url_request.h
@@ -318,6 +318,9 @@
   BLINK_PLATFORM_EXPORT network::OptionalTrustTokenParams TrustTokenParams()
       const;
 
+  BLINK_PLATFORM_EXPORT base::Optional<base::UnguessableToken> WebBundleToken()
+      const;
+
 #if INSIDE_BLINK
   BLINK_PLATFORM_EXPORT ResourceRequest& ToMutableResourceRequest();
   BLINK_PLATFORM_EXPORT const ResourceRequest& ToResourceRequest() const;
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index ed4f80eb..45b2210a7 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -393,7 +393,8 @@
     return;
   }
 
-  if (IsA<HTMLSummaryElement>(element)) {
+  if (IsA<HTMLSummaryElement>(element) &&
+      !RuntimeEnabledFeatures::SummaryListItemEnabled()) {
     // <summary> should be a list item by default, but currently it's a block
     // and the disclosure symbol is not a ::marker (bug 590014). If an author
     // specifies 'display: list-item', the <summary> would seem to have two
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index ffcb9bb4..11209da 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -286,8 +286,6 @@
       map_.Add(property.GetCSSPropertyName(), e.Priority());
     }
   }
-
-  MaybeUseCountSummaryDisplayBlock();
 }
 
 void StyleCascade::AnalyzeInterpolations() {
@@ -997,20 +995,6 @@
     CountUse(WebFeature::kCSSKeywordRevert);
 }
 
-// TODO(crbug.com/590014): Remove this when display type of <summary> is fixed
-void StyleCascade::MaybeUseCountSummaryDisplayBlock() {
-  if (!state_.GetElement().HasTagName(html_names::kSummaryTag))
-    return;
-  CascadePriority priority = map_.At(CSSPropertyName(CSSPropertyID::kDisplay));
-  if (priority.GetOrigin() <= CascadeOrigin::kUserAgent)
-    return;
-  const CSSValue* value = ValueAt(match_result_, priority.GetPosition());
-  if (auto* identifier = DynamicTo<CSSIdentifierValue>(value)) {
-    if (identifier->GetValueID() == CSSValueID::kBlock)
-      CountUse(WebFeature::kSummaryElementWithDisplayBlockAuthorRule);
-  }
-}
-
 void StyleCascade::MaybeUseCountInvalidVariableUnset(
     const CustomProperty& property) {
   if (!property.SupportsGuaranteedInvalid())
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index a62367d..0807541 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -3385,55 +3385,6 @@
             cascade.ComputedValue("--above-limit-reference-fallback"));
 }
 
-TEST_F(StyleCascadeTest, UseCountSummary) {
-  const auto feature = WebFeature::kSummaryElementWithDisplayBlockAuthorRule;
-
-  Element* summary = GetDocument().CreateRawElement(html_names::kSummaryTag);
-  GetDocument().body()->AppendChild(summary);
-
-  {
-    ASSERT_FALSE(GetDocument().IsUseCounted(feature));
-
-    TestCascade cascade(GetDocument(), summary);
-    cascade.Apply();
-    EXPECT_FALSE(GetDocument().IsUseCounted(feature));
-  }
-  {
-    ASSERT_FALSE(GetDocument().IsUseCounted(feature));
-
-    TestCascade cascade(GetDocument(), summary);
-    cascade.Add("color:green");
-    cascade.Apply();
-    EXPECT_FALSE(GetDocument().IsUseCounted(feature));
-  }
-  {
-    ASSERT_FALSE(GetDocument().IsUseCounted(feature));
-
-    TestCascade cascade(GetDocument(), summary);
-    cascade.Add("display:block", CascadeOrigin::kUserAgent);
-    cascade.Apply();
-    EXPECT_FALSE(GetDocument().IsUseCounted(feature));
-  }
-  {
-    ASSERT_FALSE(GetDocument().IsUseCounted(feature));
-
-    TestCascade cascade(GetDocument(), summary);
-    cascade.Add("display:block", CascadeOrigin::kUser);
-    cascade.Apply();
-    EXPECT_TRUE(GetDocument().IsUseCounted(feature));
-    GetDocument().ClearUseCounterForTesting(feature);
-  }
-  {
-    ASSERT_FALSE(GetDocument().IsUseCounted(feature));
-
-    TestCascade cascade(GetDocument(), summary);
-    cascade.Add("display:block", CascadeOrigin::kAuthor);
-    cascade.Apply();
-    EXPECT_TRUE(GetDocument().IsUseCounted(feature));
-    GetDocument().ClearUseCounterForTesting(feature);
-  }
-}
-
 TEST_F(StyleCascadeTest, GetCascadedValues) {
   TestCascade cascade(GetDocument());
   cascade.Add("top:1px", CascadeOrigin::kUserAgent);
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index 6b48bbf..fdc440c8 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -1243,12 +1243,6 @@
           return false;
         if (element.ShadowPseudoId() != selector.Value())
           return false;
-        if (!is_ua_rule_ &&
-            selector.Value() ==
-                shadow_element_names::kPseudoWebKitDetailsMarker) {
-          UseCounter::Count(element.GetDocument(),
-                            WebFeature::kCSSSelectorPseudoWebKitDetailsMarker);
-        }
         return true;
       }
       return false;
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 3242875..815b54c 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -2916,29 +2916,6 @@
                                     GetCSSPropertyColor()));
 }
 
-TEST_F(StyleEngineTest, SummaryDisplayUseCount) {
-  // Should not be use-counted: wrong element type.
-  GetDocument().body()->setInnerHTML(
-      "<style>div { display: block; }</style><div></div>");
-  UpdateAllLifecyclePhases();
-  EXPECT_FALSE(GetDocument().IsUseCounted(
-      WebFeature::kSummaryElementWithDisplayBlockAuthorRule));
-
-  // Should not be use-counted: wrong display type:
-  GetDocument().body()->setInnerHTML(
-      "<style>summary { display: inline; }</style><summary></summary>");
-  UpdateAllLifecyclePhases();
-  EXPECT_FALSE(GetDocument().IsUseCounted(
-      WebFeature::kSummaryElementWithDisplayBlockAuthorRule));
-
-  // Should be use-counted:
-  GetDocument().body()->setInnerHTML(
-      "<style>summary { display: block; }</style><summary></summary>");
-  UpdateAllLifecyclePhases();
-  EXPECT_TRUE(GetDocument().IsUseCounted(
-      WebFeature::kSummaryElementWithDisplayBlockAuthorRule));
-}
-
 TEST_F(StyleEngineTest, RevertUseCount) {
   GetDocument().body()->setInnerHTML(
       "<style>div { display: unset; }</style><div></div>");
diff --git a/third_party/blink/renderer/core/editing/selection_adjuster.cc b/third_party/blink/renderer/core/editing/selection_adjuster.cc
index 3e523062..a623247 100644
--- a/third_party/blink/renderer/core/editing/selection_adjuster.cc
+++ b/third_party/blink/renderer/core/editing/selection_adjuster.cc
@@ -43,9 +43,10 @@
 SelectionTemplate<Strategy> ComputeAdjustedSelection(
     const SelectionTemplate<Strategy> selection,
     const EphemeralRangeTemplate<Strategy>& range) {
-  if (range.IsCollapsed()) {
+  if (range.StartPosition().CompareTo(range.EndPosition()) == 0) {
     return typename SelectionTemplate<Strategy>::Builder()
-        .Collapse(range.StartPosition())
+        .Collapse(selection.IsBaseFirst() ? range.StartPosition()
+                                          : range.EndPosition())
         .Build();
   }
   if (selection.IsBaseFirst()) {
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
index 9ae01a2..75f9a5b1 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
@@ -480,6 +480,16 @@
     it.value->DispatchBufferedTouchEvents();
 }
 
+void WebDevToolsAgentImpl::PageScrollStarted() {
+  for (auto& it : overlay_agents_)
+    it.value->PageScrollStarted();
+}
+
+void WebDevToolsAgentImpl::PageScrollEnded() {
+  for (auto& it : overlay_agents_)
+    it.value->PageScrollEnded();
+}
+
 WebInputEventResult WebDevToolsAgentImpl::HandleInputEvent(
     const WebInputEvent& event) {
   for (auto& it : overlay_agents_) {
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
index 6bde1bb..dfa2301 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
@@ -81,6 +81,8 @@
 
   WebInputEventResult HandleInputEvent(const WebInputEvent&);
   void DispatchBufferedTouchEvents();
+  void PageScrollStarted();
+  void PageScrollEnded();
   void BindReceiver(
       mojo::PendingAssociatedRemote<mojom::blink::DevToolsAgentHost>,
       mojo::PendingAssociatedReceiver<mojom::blink::DevToolsAgent>);
diff --git a/third_party/blink/renderer/core/frame/DEPS b/third_party/blink/renderer/core/frame/DEPS
index 88ccea2..cae0b42 100644
--- a/third_party/blink/renderer/core/frame/DEPS
+++ b/third_party/blink/renderer/core/frame/DEPS
@@ -35,6 +35,7 @@
   ],
   "web_frame_widget_impl\.cc": [
     "+cc/trees/swap_promise.h",
+    "+cc/trees/compositor_commit_data.h",
   ],
   "web_frame_widget_impl\.h": [
     "+services/viz/public/mojom/hit_test/input_target_client.mojom-blink.h",
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index 95f0aa2..d5a1332a 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -579,6 +579,16 @@
               ReplacedWillBeRemoved("RTP data channels",
                                     "standard SCTP data channels", kM90,
                                     "6485681910054912")};
+
+    case WebFeature::kCSSSelectorWebkitDetailsMarker:
+      if (!RuntimeEnabledFeatures::SummaryListItemEnabled())
+        return {"NotDeprecated", kUnknown, ""};
+      return {"CSSSeelctorWebKitDetailsMarker", kM89,
+              ReplacedBy("::-webkit-details-marker pseudo element selector",
+                         "::marker") +
+                  " See https://chromestatus.com/feature/6730096436051968 for "
+                  "more details."};
+
     // Features that aren't deprecated don't have a deprecation message.
     default:
       return {"NotDeprecated", kUnknown, ""};
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 7756c0e0..083d1dc 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -39,6 +39,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "cc/trees/compositor_commit_data.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/swap_promise.h"
 #include "cc/trees/ukm_manager.h"
@@ -1210,6 +1211,9 @@
 
 void WebFrameWidgetImpl::SendScrollEndEventFromImplSide(
     cc::ElementId scroll_latched_element_id) {
+  if (WebDevToolsAgentImpl* devtools = LocalRootImpl()->DevToolsAgentImpl())
+    devtools->PageScrollEnded();
+
   if (!RuntimeEnabledFeatures::OverscrollCustomizationEnabled())
     return;
 
@@ -1219,6 +1223,31 @@
     target_node->GetDocument().EnqueueScrollEndEventForNode(target_node);
 }
 
+void WebFrameWidgetImpl::UpdateCompositorScrollState(
+    const cc::CompositorCommitData& commit_data) {
+  if (commit_data.manipulation_info != cc::kManipulationInfoNone) {
+    if (WebDevToolsAgentImpl* devtools = LocalRootImpl()->DevToolsAgentImpl())
+      devtools->PageScrollStarted();
+  }
+
+  RecordManipulationTypeCounts(commit_data.manipulation_info);
+
+  if (commit_data.scroll_latched_element_id == cc::ElementId())
+    return;
+
+  if (!commit_data.overscroll_delta.IsZero()) {
+    SendOverscrollEventFromImplSide(commit_data.overscroll_delta,
+                                    commit_data.scroll_latched_element_id);
+  }
+
+  // TODO(bokan): If a scroll ended and a new one began in the same Blink frame
+  // (e.g. during a long running main thread task), this will erroneously
+  // dispatch the scroll end to the latter (still-scrolling) element.
+  // https://crbug.com/1116780.
+  if (commit_data.scroll_gesture_did_end)
+    SendScrollEndEventFromImplSide(commit_data.scroll_latched_element_id);
+}
+
 WebInputMethodController*
 WebFrameWidgetImpl::GetActiveWebInputMethodController() const {
   WebLocalFrameImpl* local_frame =
@@ -2117,6 +2146,7 @@
   // Manipulation counts are only recorded for the main frame.
   if (!ForMainFrame())
     return;
+
   if ((info & cc::kManipulationInfoWheel) == cc::kManipulationInfoWheel) {
     UseCounter::Count(LocalRootImpl()->GetDocument(),
                       WebFeature::kScrollByWheel);
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 2206f2b..335ad83 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -278,11 +278,8 @@
 
   // WebFrameWidget overrides.
   WebLocalFrame* LocalRoot() const override;
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override;
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override;
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override;
   WebInputMethodController* GetActiveWebInputMethodController() const override;
   WebLocalFrameImpl* FocusedWebLocalFrameInWidget() const override;
   bool ScrollFocusedEditableElementIntoView() override;
@@ -584,7 +581,6 @@
   void BeginCommitCompositorFrame() override;
   void EndCommitCompositorFrame(base::TimeTicks commit_start_time) override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
-  void RecordManipulationTypeCounts(cc::ManipulationInfo info) override;
   void RecordDispatchRafAlignedInputTime(
       base::TimeTicks raf_aligned_input_start_time) override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) override;
@@ -746,6 +742,12 @@
 
   void SetWindowRectSynchronously(const gfx::Rect& new_window_rect);
 
+  void SendOverscrollEventFromImplSide(const gfx::Vector2dF& overscroll_delta,
+                                       cc::ElementId scroll_latched_element_id);
+  void SendScrollEndEventFromImplSide(cc::ElementId scroll_latched_element_id);
+
+  void RecordManipulationTypeCounts(cc::ManipulationInfo info);
+
   // Finds the parameters required for scrolling the focused editable |element|
   // into view. |out_rect_to_scroll| is used for recursive scrolling of the
   // element into view and contains all or part of element's bounding box and
diff --git a/third_party/blink/renderer/core/html/html_details_element.cc b/third_party/blink/renderer/core/html/html_details_element.cc
index 42c8395e..43a31f0 100644
--- a/third_party/blink/renderer/core/html/html_details_element.cc
+++ b/third_party/blink/renderer/core/html/html_details_element.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
+#include "third_party/blink/renderer/core/html/html_style_element.h"
 #include "third_party/blink/renderer/core/html/html_summary_element.h"
 #include "third_party/blink/renderer/core/html/shadow/details_marker_control.h"
 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
@@ -95,6 +96,22 @@
   content_slot->SetInlineStyleProperty(CSSPropertyID::kDisplay,
                                        CSSValueID::kNone);
   root.AppendChild(content_slot);
+
+  if (RuntimeEnabledFeatures::SummaryListItemEnabled()) {
+    auto* default_summary_style = MakeGarbageCollected<HTMLStyleElement>(
+        GetDocument(), CreateElementFlags::ByCreateElement());
+    default_summary_style->setTextContent(R"CSS(
+:host summary {
+  display: list-item;
+  counter-increment: list-item 0;
+  list-style: disclosure-closed inside;
+}
+:host([open]) summary {
+  list-style-type: disclosure-open;
+}
+)CSS");
+    root.AppendChild(default_summary_style);
+  }
 }
 
 Element* HTMLDetailsElement::FindMainSummary() const {
@@ -134,6 +151,8 @@
                                       CSSValueID::kNone);
     }
 
+    if (RuntimeEnabledFeatures::SummaryListItemEnabled())
+      return;
     // Invalidate the LayoutDetailsMarker in order to turn the arrow signifying
     // if the details element is open or closed.
     Element* summary = FindMainSummary();
diff --git a/third_party/blink/renderer/core/html/html_summary_element.cc b/third_party/blink/renderer/core/html/html_summary_element.cc
index aa9c582e..953846b 100644
--- a/third_party/blink/renderer/core/html/html_summary_element.cc
+++ b/third_party/blink/renderer/core/html/html_summary_element.cc
@@ -38,8 +38,10 @@
 
 HTMLSummaryElement::HTMLSummaryElement(Document& document)
     : HTMLElement(html_names::kSummaryTag, document) {
-  SetHasCustomStyleCallbacks();
-  EnsureUserAgentShadowRoot();
+  if (!RuntimeEnabledFeatures::SummaryListItemEnabled()) {
+    SetHasCustomStyleCallbacks();
+    EnsureUserAgentShadowRoot();
+  }
 }
 
 LayoutObject* HTMLSummaryElement::CreateLayoutObject(const ComputedStyle& style,
@@ -57,6 +59,7 @@
 }
 
 void HTMLSummaryElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+  DCHECK(!RuntimeEnabledFeatures::SummaryListItemEnabled());
   auto* marker_control =
       MakeGarbageCollected<DetailsMarkerControl>(GetDocument());
   marker_control->SetIdAttribute(shadow_element_names::kIdDetailsMarker);
@@ -73,6 +76,7 @@
 }
 
 Element* HTMLSummaryElement::MarkerControl() {
+  DCHECK(!RuntimeEnabledFeatures::SummaryListItemEnabled());
   return EnsureUserAgentShadowRoot().getElementById(
       shadow_element_names::kIdDetailsMarker);
 }
@@ -161,6 +165,7 @@
 }
 
 void HTMLSummaryElement::WillRecalcStyle(const StyleRecalcChange) {
+  DCHECK(!RuntimeEnabledFeatures::SummaryListItemEnabled());
   if (GetForceReattachLayoutTree() && IsMainSummary()) {
     if (Element* marker = MarkerControl()) {
       marker->SetNeedsStyleRecalc(
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
index ec4550e0..7295378 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
@@ -919,6 +919,16 @@
   OverlayMainFrame()->GetEventHandler().DispatchBufferedTouchEvents();
 }
 
+void InspectorOverlayAgent::PageScrollStarted() {
+  // The new scroll can start without the previous ending.
+  is_page_scrolling_ = true;
+}
+
+void InspectorOverlayAgent::PageScrollEnded() {
+  DCHECK(is_page_scrolling_);
+  is_page_scrolling_ = false;
+}
+
 WebInputEventResult InspectorOverlayAgent::HandleInputEvent(
     const WebInputEvent& input_event) {
   if (!enabled_.Get())
@@ -1046,9 +1056,13 @@
   float scale = WindowToViewportScale();
 
   if (inspect_tool_) {
-    inspect_tool_->Draw(scale);
-    if (persistent_tool_ && inspect_tool_->SupportsPersistentOverlays())
+    // Skip drawing persistent_tool_ on page scroll.
+    if (!(inspect_tool_ == persistent_tool_ && is_page_scrolling_))
+      inspect_tool_->Draw(scale);
+    if (persistent_tool_ && inspect_tool_->SupportsPersistentOverlays() &&
+        !is_page_scrolling_) {
       persistent_tool_->Draw(scale);
+    }
   }
 
   if (hinge_)
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
index 4ddfb82..85acd79 100644
--- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
@@ -233,6 +233,8 @@
   void Inspect(Node*);
   void EnsureAXContext(Node*);
   void DispatchBufferedTouchEvents();
+  void PageScrollStarted();
+  void PageScrollEnded();
   WebInputEventResult HandleInputEvent(const WebInputEvent&);
   WebInputEventResult HandleInputEventInOverlay(const WebInputEvent&);
   void PageLayoutInvalidated(bool resized);
@@ -303,6 +305,7 @@
   HeapHashMap<WeakMember<Document>, std::unique_ptr<AXContext>>
       document_to_ax_context_;
   bool swallow_next_mouse_up_;
+  bool is_page_scrolling_ = false;
   DOMNodeId backend_node_id_to_inspect_;
   InspectorAgentState::Boolean enabled_;
   InspectorAgentState::Boolean show_ad_highlights_;
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index bf9f2d8..efd8d11 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -147,6 +147,22 @@
       return object;
     if (skip_info)
       skip_info->Update(*object);
+
+    // According to the HTML standard, a rendered legend is a child of a
+    // fieldset. However a rendered legend is a child of an anonymous fieldset
+    // content box in a LayoutObject tree.  NG fragment trees follow the
+    // structure of the standard.
+    //
+    // The following code resolves this inconsistency, and we skip anonymous
+    // fieldset content boxes if |descendant| is in a rendered legend.
+    if (UNLIKELY(object->IsRenderedLegend())) {
+      LayoutObject* legend_parent = object->Parent();
+      if (legend_parent->IsAnonymous()) {
+        if (skip_info)
+          skip_info->Update(*legend_parent);
+        object = legend_parent;
+      }
+    }
   }
   return nullptr;
 }
diff --git a/third_party/blink/renderer/core/layout/layout_theme.cc b/third_party/blink/renderer/core/layout/layout_theme.cc
index 36571d8..de02277e 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme.cc
@@ -293,6 +293,26 @@
 }
 
 String LayoutTheme::ExtraDefaultStyleSheet() {
+  if (RuntimeEnabledFeatures::SummaryListItemEnabled()) {
+    // https://html.spec.whatwg.org/C/#the-details-and-summary-elements
+    // The specification doesn't have |details >| and |:first-of-type|.
+    // We add them because:
+    //  - We had provided |summary { display: block }| for a long time,
+    //    there are sites using <summary> without details, and they
+    //    expect that <summary> is not a list-item.
+    //  - Firefox does so.
+    return String(R"CSS(
+details > summary:first-of-type {
+    display: list-item;
+    counter-increment: list-item 0;
+    list-style: disclosure-closed inside;
+}
+
+details[open] > summary:first-of-type {
+    list-style-type: disclosure-open;
+}
+)CSS");
+  }
   return g_empty_string;
 }
 
diff --git a/third_party/blink/renderer/core/layout/list_marker.cc b/third_party/blink/renderer/core/layout/list_marker.cc
index e910870..2974419 100644
--- a/third_party/blink/renderer/core/layout/list_marker.cc
+++ b/third_party/blink/renderer/core/layout/list_marker.cc
@@ -23,11 +23,14 @@
 // Recommended UA margin for list markers.
 const int kCUAMarkerMarginEm = 1;
 
+// 'closure-*' have 0.4em margin for compatibility with
+// ::-webkit-details-marker.
+const float kClosureMarkerMarginEm = 0.4f;
+
 namespace {
 
-LayoutUnit DisclosureSymbolSize(const FontMetrics& font_metrics) {
-  LayoutUnit font_height(font_metrics.Height());
-  return font_height * 2 / 3;
+LayoutUnit DisclosureSymbolSize(const ComputedStyle& style) {
+  return LayoutUnit(style.SpecifiedFontSize() * 0.66);
 }
 
 }  // namespace
@@ -293,7 +296,7 @@
   const auto type = style.ListStyleType();
   if (type == EListStyleType::kDisclosureOpen ||
       type == EListStyleType::kDisclosureClosed) {
-    return 1 + DisclosureSymbolSize(font_data->GetFontMetrics()) + 1;
+    return DisclosureSymbolSize(style);
   }
   return LayoutUnit((font_data->GetFontMetrics().Ascent() * 2 / 3 + 1) / 2 + 2);
 }
@@ -305,8 +308,14 @@
     return {};
   if (list_item_style.GeneratesMarkerImage())
     return {LayoutUnit(), LayoutUnit(kCMarkerPaddingPx)};
-  switch (GetListStyleCategory(list_item_style.ListStyleType())) {
+  auto type = list_item_style.ListStyleType();
+  switch (GetListStyleCategory(type)) {
     case ListStyleCategory::kSymbol:
+      if (type == EListStyleType::kDisclosureOpen ||
+          type == EListStyleType::kDisclosureClosed) {
+        return {LayoutUnit(), LayoutUnit(kClosureMarkerMarginEm *
+                                         marker_style.SpecifiedFontSize())};
+      }
       return {LayoutUnit(-1),
               LayoutUnit(kCUAMarkerMarginEm * marker_style.ComputedFontSize())};
     default:
@@ -339,7 +348,7 @@
         const FontMetrics& font_metrics = font_data->GetFontMetrics();
         LayoutUnit offset = (type == EListStyleType::kDisclosureOpen ||
                              type == EListStyleType::kDisclosureClosed)
-                                ? DisclosureSymbolSize(font_metrics)
+                                ? DisclosureSymbolSize(marker_style)
                                 : LayoutUnit(font_metrics.Ascent() * 2 / 3);
         margin_start = -offset - kCMarkerPaddingPx - 1;
         margin_end = offset + kCMarkerPaddingPx + 1 - marker_inline_size;
@@ -364,14 +373,14 @@
   // TODO(wkorman): Review and clean up/document the calculations below.
   // http://crbug.com/543193
   const FontMetrics& font_metrics = font_data->GetFontMetrics();
+  const int ascent = font_metrics.Ascent();
   const auto type = style.ListStyleType();
   if (type == EListStyleType::kDisclosureOpen ||
       type == EListStyleType::kDisclosureClosed) {
-    LayoutUnit marker_size = DisclosureSymbolSize(font_metrics);
-    relative_rect =
-        LayoutRect(LayoutUnit(1), marker_size / 4, marker_size, marker_size);
+    LayoutUnit marker_size = DisclosureSymbolSize(style);
+    relative_rect = LayoutRect(LayoutUnit(), ascent - marker_size, marker_size,
+                               marker_size);
   } else {
-    int ascent = font_metrics.Ascent();
     int bullet_width = (ascent * 2 / 3 + 1) / 2;
     relative_rect = LayoutRect(1, 3 * (ascent - ascent * 2 / 3) / 2,
                                bullet_width, bullet_width);
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc
index 050868a5..cf4cd3d 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.cc
@@ -113,6 +113,13 @@
   child_style.SetOverflowX(StyleRef().OverflowX());
   child_style.SetOverflowY(StyleRef().OverflowY());
   child_style.SetUnicodeBidi(StyleRef().GetUnicodeBidi());
+
+  // If the FIELDSET is an OOF container, the anonymous content box should be
+  // an OOF container to steal OOF objects under the FIELDSET.
+  if (CanContainFixedPositionObjects())
+    child_style.SetContain(kContainsPaint);
+  else if (StyleRef().CanContainAbsolutePositionObjects())
+    child_style.SetPosition(EPosition::kRelative);
 }
 
 bool LayoutNGFieldset::IsOfType(LayoutObjectType type) const {
diff --git a/third_party/blink/renderer/core/paint/list_marker_painter.cc b/third_party/blink/renderer/core/paint/list_marker_painter.cc
index 9c8e1cf8..6e03b0e 100644
--- a/third_party/blink/renderer/core/paint/list_marker_painter.cc
+++ b/third_party/blink/renderer/core/paint/list_marker_painter.cc
@@ -25,7 +25,7 @@
 void ListMarkerPainter::PaintSymbol(const PaintInfo& paint_info,
                                     const LayoutObject* object,
                                     const ComputedStyle& style,
-                                    const IntRect& marker) {
+                                    const LayoutRect& marker) {
   DCHECK(object);
   GraphicsContext& context = paint_info.context;
   ScopedDarkModeElementRoleOverride list_symbol(
@@ -39,15 +39,16 @@
   context.SetStrokeColor(color);
   context.SetStrokeStyle(kSolidStroke);
   context.SetStrokeThickness(1.0f);
+  IntRect snapped_rect = PixelSnappedIntRect(marker);
   switch (style.ListStyleType()) {
     case EListStyleType::kDisc:
-      context.FillEllipse(FloatRect(marker));
+      context.FillEllipse(FloatRect(snapped_rect));
       break;
     case EListStyleType::kCircle:
-      context.StrokeEllipse(FloatRect(marker));
+      context.StrokeEllipse(FloatRect(snapped_rect));
       break;
     case EListStyleType::kSquare:
-      context.FillRect(marker);
+      context.FillRect(snapped_rect);
       break;
     case EListStyleType::kDisclosureOpen:
     case EListStyleType::kDisclosureClosed: {
@@ -113,7 +114,7 @@
 
   if (style_category == ListMarker::ListStyleCategory::kSymbol) {
     PaintSymbol(paint_info, &layout_list_marker_,
-                layout_list_marker_.StyleRef(), PixelSnappedIntRect(marker));
+                layout_list_marker_.StyleRef(), marker);
     return;
   }
 
diff --git a/third_party/blink/renderer/core/paint/list_marker_painter.h b/third_party/blink/renderer/core/paint/list_marker_painter.h
index 86c2023c..830de9d 100644
--- a/third_party/blink/renderer/core/paint/list_marker_painter.h
+++ b/third_party/blink/renderer/core/paint/list_marker_painter.h
@@ -11,9 +11,9 @@
 
 struct PaintInfo;
 class ComputedStyle;
-class IntRect;
 class LayoutListMarker;
 class LayoutObject;
+class LayoutRect;
 
 class ListMarkerPainter {
   STACK_ALLOCATED();
@@ -27,7 +27,7 @@
   static void PaintSymbol(const PaintInfo&,
                           const LayoutObject*,
                           const ComputedStyle&,
-                          const IntRect&);
+                          const LayoutRect&);
 
  private:
   const LayoutListMarker& layout_list_marker_;
diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
index 4de1aaa..e965574 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
@@ -504,9 +504,8 @@
   PhysicalRect marker_rect(
       ListMarker::RelativeSymbolMarkerRect(style, box_size.width));
   marker_rect.Move(paint_offset);
-  IntRect rect = PixelSnappedIntRect(marker_rect);
-
-  ListMarkerPainter::PaintSymbol(paint_info, layout_object, style, rect);
+  ListMarkerPainter::PaintSymbol(paint_info, layout_object, style,
+                                 marker_rect.ToLayoutRect());
 }
 
 // This is copied from InlineTextBoxPainter::PaintSelection() but lacks of
diff --git a/third_party/blink/renderer/core/streams/readable_stream.cc b/third_party/blink/renderer/core/streams/readable_stream.cc
index 8e54f5d3..99da52b 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream.cc
@@ -1246,6 +1246,10 @@
   // https://streams.spec.whatwg.org/#rs-get-reader
   if (options->hasMode()) {
     DCHECK_EQ(options->mode(), "byob");
+
+    UseCounter::Count(ExecutionContext::From(script_state),
+                      WebFeature::kReadableStreamBYOBReader);
+
     return_value.SetReadableStreamBYOBReader(
         AcquireBYOBReader(script_state, this, exception_state));
   } else {
@@ -1415,6 +1419,10 @@
         exception_state.ThrowRangeError("bytes type is not yet implemented");
         return;
       }
+
+      UseCounter::Count(ExecutionContext::From(script_state),
+                        WebFeature::kReadableStreamWithByteSource);
+
       UnderlyingSource* underlying_source_dict = UnderlyingSource::Create();
       V8UnderlyingSource::ToImpl(script_state->GetIsolate(),
                                  raw_underlying_source.V8Value(),
@@ -1893,21 +1901,23 @@
                                             DOMArrayBufferView* chunk,
                                             bool done) {
   // https://streams.spec.whatwg.org/#readable-stream-fulfill-read-into-request
-  // 1. Let reader be stream.[[reader]].
+  // 1. Assert: ! ReadableStreamHasBYOBReader(stream) is true.
+  DCHECK(HasBYOBReader(stream));
+  // 2. Let reader be stream.[[reader]].
   ReadableStreamGenericReader* reader = stream->reader_;
   ReadableStreamBYOBReader* byob_reader = To<ReadableStreamBYOBReader>(reader);
-  // 2. Assert: reader.[[readIntoRequests]] is not empty.
+  // 3. Assert: reader.[[readIntoRequests]] is not empty.
   DCHECK(!byob_reader->read_into_requests_.IsEmpty());
-  // 3. Let readIntoRequest be reader.[[readIntoRequests]][0].
+  // 4. Let readIntoRequest be reader.[[readIntoRequests]][0].
   ReadableStreamBYOBReader::ReadIntoRequest* read_into_request =
       byob_reader->read_into_requests_[0];
-  // 4. Remove readIntoRequest from reader.[[readIntoRequests]].
+  // 5. Remove readIntoRequest from reader.[[readIntoRequests]].
   byob_reader->read_into_requests_.pop_front();
-  // 5. If done is true, perform readIntoRequest’s close steps, given chunk.
+  // 6. If done is true, perform readIntoRequest’s close steps, given chunk.
   if (done) {
     read_into_request->CloseSteps(script_state, chunk);
   } else {
-    // 6. Otherwise, perform readIntoRequest’s chunk steps, given chunk.
+    // 7. Otherwise, perform readIntoRequest’s chunk steps, given chunk.
     read_into_request->ChunkSteps(script_state, chunk);
   }
 }
@@ -1917,20 +1927,23 @@
                                         v8::Local<v8::Value> chunk,
                                         bool done) {
   // https://streams.spec.whatwg.org/#readable-stream-fulfill-read-request
-  // 1. Let reader be stream.[[reader]].
+  // 1. Assert: ! ReadableStreamHasDefaultReader(stream) is true.
+  DCHECK(HasDefaultReader(stream));
+
+  // 2. Let reader be stream.[[reader]].
   ReadableStreamGenericReader* reader = stream->reader_;
   ReadableStreamDefaultReader* default_reader =
       To<ReadableStreamDefaultReader>(reader);
 
-  // 2. Let readRequest be the first element of reader.[[readRequests]].
+  // 3. Let readRequest be the first element of reader.[[readRequests]].
   StreamPromiseResolver* read_request = default_reader->read_requests_.front();
 
-  // 3. Remove readIntoRequest from reader.[[readIntoRequests]], shifting all
+  // 4. Remove readIntoRequest from reader.[[readIntoRequests]], shifting all
   //    other elements downward (so that the second becomes the first, and so
   //    on).
   default_reader->read_requests_.pop_front();
 
-  // 4. Resolve readIntoRequest.[[promise]] with !
+  // 5. Resolve readIntoRequest.[[promise]] with !
   //    ReadableStreamCreateReadResult(chunk, done, reader.[[forAuthorCode]]).
   read_request->Resolve(script_state, ReadableStream::CreateReadResult(
                                           script_state, chunk, done,
@@ -1939,14 +1952,18 @@
 
 int ReadableStream::GetNumReadIntoRequests(const ReadableStream* stream) {
   // https://streams.spec.whatwg.org/#readable-stream-get-num-read-into-requests
-  // 1. Return stream.[[reader]].[[readIntoRequests]]'s size.
+  // 1. Assert: ! ReadableStreamHasBYOBReader(stream) is true.
+  DCHECK(HasBYOBReader(stream));
+  // 2. Return stream.[[reader]].[[readIntoRequests]]'s size.
   ReadableStreamGenericReader* reader = stream->reader_;
   return To<ReadableStreamBYOBReader>(reader)->read_into_requests_.size();
 }
 
 int ReadableStream::GetNumReadRequests(const ReadableStream* stream) {
   // https://streams.spec.whatwg.org/#readable-stream-get-num-read-requests
-  // 1. Return the number of elements in stream.[[reader]].[[readRequests]].
+  // 1. Assert: ! ReadableStreamHasDefaultReader(stream) is true.
+  DCHECK(HasDefaultReader(stream));
+  // 2. Return the number of elements in stream.[[reader]].[[readRequests]].
   ReadableStreamGenericReader* reader = stream->reader_;
   return To<ReadableStreamDefaultReader>(reader)->read_requests_.size();
 }
diff --git a/third_party/blink/renderer/core/streams/readable_stream_generic_reader.cc b/third_party/blink/renderer/core/streams/readable_stream_generic_reader.cc
index 52f6564..7168d98f 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_generic_reader.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_generic_reader.cc
@@ -128,7 +128,7 @@
   reader->owner_readable_stream_ = stream;
 
   // 2. Set stream.[[reader]] to reader.
-  stream->reader_ = static_cast<ReadableStreamDefaultReader*>(reader);
+  stream->reader_ = reader;
 
   switch (stream->state_) {
     // 3. If stream.[[state]] is "readable",
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.cc b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
index 204acba..3ab291d2 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
@@ -406,9 +406,8 @@
       capabilities->setPan(capabilities_->pan());
     if (capabilities_->hasTilt())
       capabilities->setTilt(capabilities_->tilt());
-  }
-  if (capabilities_->hasZoom() && HasZoomPermissionGranted()) {
-    capabilities->setZoom(capabilities_->zoom());
+    if (capabilities_->hasZoom())
+      capabilities->setZoom(capabilities_->zoom());
   }
 
   if (capabilities_->hasTorch())
@@ -486,7 +485,7 @@
       (constraints->hasTilt() &&
        !(capabilities_->hasTilt() && HasPanTiltZoomPermissionGranted())) ||
       (constraints->hasZoom() &&
-       !(capabilities_->hasZoom() && HasZoomPermissionGranted())) ||
+       !(capabilities_->hasZoom() && HasPanTiltZoomPermissionGranted())) ||
       (constraints->hasTorch() && !capabilities_->hasTorch())) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kNotSupportedError, "Unsupported constraint(s)"));
@@ -885,9 +884,8 @@
       settings->setPan(settings_->pan());
     if (settings_->hasTilt())
       settings->setTilt(settings_->tilt());
-  }
-  if (settings_->hasZoom() && HasZoomPermissionGranted()) {
-    settings->setZoom(settings_->zoom());
+    if (settings_->hasZoom())
+      settings->setZoom(settings_->zoom());
   }
 
   if (settings_->hasTorch())
@@ -951,13 +949,6 @@
 }
 
 bool ImageCapture::HasPanTiltZoomPermissionGranted() const {
-  if (!RuntimeEnabledFeatures::MediaCapturePanTiltEnabled())
-    return false;
-
-  return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
-}
-
-bool ImageCapture::HasZoomPermissionGranted() const {
   return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
 }
 
@@ -1177,8 +1168,6 @@
       capabilities_->setTilt(ToMediaSettingsRange(*photo_state->tilt));
       settings_->setTilt(photo_state->tilt->current);
     }
-  }
-  if (HasZoomPermissionGranted()) {
     if (photo_state->zoom->max != photo_state->zoom->min) {
       capabilities_->setZoom(ToMediaSettingsRange(*photo_state->zoom));
       settings_->setZoom(photo_state->zoom->current);
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.h b/third_party/blink/renderer/modules/imagecapture/image_capture.h
index c897750..1bc6298 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.h
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.h
@@ -84,7 +84,6 @@
   void GetMediaTrackSettings(MediaTrackSettings*) const;
 
   bool HasPanTiltZoomPermissionGranted() const;
-  bool HasZoomPermissionGranted() const;
 
   // Called by MediaStreamTrack::clone() to get a clone with same capabilities,
   // settings, and constraints.
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index 5b7c33d6..d27522d9 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -89,12 +89,9 @@
          constraint_set->hasColorTemperature() || constraint_set->hasIso() ||
          constraint_set->hasBrightness() || constraint_set->hasContrast() ||
          constraint_set->hasSaturation() || constraint_set->hasSharpness() ||
-         constraint_set->hasFocusDistance() ||
-         (RuntimeEnabledFeatures::MediaCapturePanTiltEnabled() &&
-          constraint_set->hasPan()) ||
-         (RuntimeEnabledFeatures::MediaCapturePanTiltEnabled() &&
-          constraint_set->hasTilt()) ||
-         constraint_set->hasZoom() || constraint_set->hasTorch();
+         constraint_set->hasFocusDistance() || constraint_set->hasPan() ||
+         constraint_set->hasTilt() || constraint_set->hasZoom() ||
+         constraint_set->hasTorch();
 }
 
 bool ConstraintSetHasNonImageCapture(
diff --git a/third_party/blink/renderer/modules/mediastream/media_track_constraint_set.idl b/third_party/blink/renderer/modules/mediastream/media_track_constraint_set.idl
index 069e691..84de1cb 100644
--- a/third_party/blink/renderer/modules/mediastream/media_track_constraint_set.idl
+++ b/third_party/blink/renderer/modules/mediastream/media_track_constraint_set.idl
@@ -48,8 +48,8 @@
     ConstrainDouble    saturation;
     ConstrainDouble    sharpness;
     ConstrainDouble    focusDistance;
-    [RuntimeEnabled=MediaCapturePanTilt] (boolean or ConstrainDouble) pan;
-    [RuntimeEnabled=MediaCapturePanTilt] (boolean or ConstrainDouble) tilt;
+    (boolean or ConstrainDouble) pan;
+    (boolean or ConstrainDouble) tilt;
     (boolean or ConstrainDouble) zoom;
     ConstrainBoolean   torch;
     // The "mandatory" and "_optional" members are retained for conformance
diff --git a/third_party/blink/renderer/modules/mediastream/media_track_settings.idl b/third_party/blink/renderer/modules/mediastream/media_track_settings.idl
index af933aa..8df714f 100644
--- a/third_party/blink/renderer/modules/mediastream/media_track_settings.idl
+++ b/third_party/blink/renderer/modules/mediastream/media_track_settings.idl
@@ -43,8 +43,8 @@
     double            saturation;
     double            sharpness;
     double            focusDistance;
-    [RuntimeEnabled=MediaCapturePanTilt] double            pan;
-    [RuntimeEnabled=MediaCapturePanTilt] double            tilt;
+    double            pan;
+    double            tilt;
     double            zoom;
     boolean           torch;
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_track_supported_constraints.idl b/third_party/blink/renderer/modules/mediastream/media_track_supported_constraints.idl
index f123499..c620762 100644
--- a/third_party/blink/renderer/modules/mediastream/media_track_supported_constraints.idl
+++ b/third_party/blink/renderer/modules/mediastream/media_track_supported_constraints.idl
@@ -44,11 +44,11 @@
     boolean iso = true;
     boolean brightness = true;
     boolean contrast = true;
-    [RuntimeEnabled=MediaCapturePanTilt] boolean pan = true;
+    boolean pan = true;
     boolean saturation = true;
     boolean sharpness = true;
     boolean focusDistance = true;
-    [RuntimeEnabled=MediaCapturePanTilt] boolean tilt = true;
+    boolean tilt = true;
     boolean zoom = true;
     boolean torch = true;
 };
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
index f331ca7..1346368 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_processor.cc
@@ -47,7 +47,6 @@
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
 #include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/video_capture/local_video_capturer_source.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
@@ -812,9 +811,6 @@
 // static
 bool UserMediaProcessor::IsPanTiltZoomPermissionRequested(
     const MediaConstraints& constraints) {
-  if (!RuntimeEnabledFeatures::MediaCapturePanTiltEnabled())
-    return false;
-
   if (constraints.Basic().pan.IsPresent() ||
       constraints.Basic().tilt.IsPresent() ||
       constraints.Basic().zoom.IsPresent()) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
index b068300..d3b17a5 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
@@ -344,8 +344,11 @@
    public:
     class MemberIterator : public RTCLegacyStatsMemberIterator {
      public:
-      MemberIterator(const StatsReport::Values::const_iterator& it,
-                     const StatsReport::Values::const_iterator& end)
+      MemberIterator(
+          const std::vector<StatsReport::Values::value_type>::const_iterator&
+              it,
+          const std::vector<StatsReport::Values::value_type>::const_iterator&
+              end)
           : it_(it), end_(end) {}
 
       // RTCLegacyStatsMemberIterator
@@ -378,8 +381,8 @@
       }
 
      private:
-      StatsReport::Values::const_iterator it_;
-      StatsReport::Values::const_iterator end_;
+      std::vector<StatsReport::Values::value_type>::const_iterator it_;
+      std::vector<StatsReport::Values::value_type>::const_iterator end_;
     };
 
     explicit Report(const StatsReport* report)
@@ -387,7 +390,7 @@
           type_(report->type()),
           type_name_(report->TypeToString()),
           timestamp_(report->timestamp()),
-          values_(report->values()) {}
+          values_(report->values().begin(), report->values().end()) {}
 
     ~Report() override {
       // Since the values vector holds pointers to const objects that are bound
@@ -411,7 +414,7 @@
     const StatsReport::StatsType type_;
     const std::string type_name_;
     const double timestamp_;
-    const StatsReport::Values values_;
+    const std::vector<StatsReport::Values::value_type> values_;
   };
 
   static void DeleteReports(std::vector<Report*>* reports) {
diff --git a/third_party/blink/renderer/modules/permissions/permission_utils.cc b/third_party/blink/renderer/modules/permissions/permission_utils.cc
index fc714091..ddfb2d3 100644
--- a/third_party/blink/renderer/modules/permissions/permission_utils.cc
+++ b/third_party/blink/renderer/modules/permissions/permission_utils.cc
@@ -115,12 +115,8 @@
     if (exception_state.HadException())
       return nullptr;
 
-    if (RuntimeEnabledFeatures::MediaCapturePanTiltEnabled()) {
-      return CreateVideoCapturePermissionDescriptor(
-          camera_device_permission->panTiltZoom());
-    }
-
-    return CreateVideoCapturePermissionDescriptor(false /* pan_tilt_zoom */);
+    return CreateVideoCapturePermissionDescriptor(
+        camera_device_permission->panTiltZoom());
   }
   if (name == "microphone")
     return CreatePermissionDescriptor(PermissionName::AUDIO_CAPTURE);
diff --git a/third_party/blink/renderer/platform/exported/web_url_request.cc b/third_party/blink/renderer/platform/exported/web_url_request.cc
index 986f0e80..9fb7e59d 100644
--- a/third_party/blink/renderer/platform/exported/web_url_request.cc
+++ b/third_party/blink/renderer/platform/exported/web_url_request.cc
@@ -548,6 +548,13 @@
   return ConvertTrustTokenParams(resource_request_->TrustTokenParams());
 }
 
+base::Optional<base::UnguessableToken> WebURLRequest::WebBundleToken() const {
+  if (resource_request_->GetWebBundleTokenParams()) {
+    return resource_request_->GetWebBundleTokenParams()->token;
+  }
+  return base::nullopt;
+}
+
 WebURLRequest::WebURLRequest(ResourceRequest& r) : resource_request_(&r) {}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
index 792cbeb..2e873fba 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.cc
@@ -30,6 +30,7 @@
 
 #include "base/unguessable_token.h"
 #include "services/network/public/mojom/ip_address_space.mojom-blink.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/platform/network/encoded_form_data.h"
@@ -40,6 +41,38 @@
 
 namespace blink {
 
+ResourceRequestHead::WebBundleTokenParams&
+ResourceRequestHead::WebBundleTokenParams::operator=(
+    const WebBundleTokenParams& other) {
+  token = other.token;
+  handle = other.CloneHandle();
+  return *this;
+}
+
+ResourceRequestHead::WebBundleTokenParams::WebBundleTokenParams(
+    const WebBundleTokenParams& other) {
+  *this = other;
+}
+
+ResourceRequestHead::WebBundleTokenParams::WebBundleTokenParams(
+    const base::UnguessableToken& web_bundle_token,
+    mojo::PendingRemote<network::mojom::WebBundleHandle> web_bundle_handle)
+    : token(web_bundle_token), handle(std::move(web_bundle_handle)) {}
+
+mojo::PendingRemote<network::mojom::WebBundleHandle>
+ResourceRequestHead::WebBundleTokenParams::CloneHandle() const {
+  if (!handle)
+    return mojo::NullRemote();
+  mojo::Remote<network::mojom::WebBundleHandle> remote(std::move(
+      const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>(
+          handle)));
+  mojo::PendingRemote<network::mojom::WebBundleHandle> new_remote;
+  remote->Clone(new_remote.InitWithNewPipeAndPassReceiver());
+  const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>(handle) =
+      remote.Unbind();
+  return new_remote;
+}
+
 const base::TimeDelta ResourceRequestHead::default_timeout_interval_ =
     base::TimeDelta::Max();
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
index d55e1fa..b018750 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_request.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.h
@@ -41,6 +41,8 @@
 #include "services/network/public/mojom/fetch_api.mojom-blink-forward.h"
 #include "services/network/public/mojom/ip_address_space.mojom-blink-forward.h"
 #include "services/network/public/mojom/trust_tokens.mojom-blink.h"
+#include "services/network/public/mojom/url_loader.mojom-blink.h"
+#include "services/network/public/mojom/web_bundle_handle.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/resource_request_blocked_reason.h"
 #include "third_party/blink/public/platform/web_url_request_extra_data.h"
@@ -80,6 +82,21 @@
         : original_url(original_url), previous_url(previous_url) {}
   };
 
+  struct PLATFORM_EXPORT WebBundleTokenParams {
+    WebBundleTokenParams() = delete;
+    WebBundleTokenParams(const WebBundleTokenParams& other);
+    WebBundleTokenParams& operator=(const WebBundleTokenParams& other);
+
+    WebBundleTokenParams(
+        const base::UnguessableToken& token,
+        mojo::PendingRemote<network::mojom::WebBundleHandle> handle);
+
+    mojo::PendingRemote<network::mojom::WebBundleHandle> CloneHandle() const;
+
+    base::UnguessableToken token;
+    mojo::PendingRemote<network::mojom::WebBundleHandle> handle;
+  };
+
   ResourceRequestHead();
   explicit ResourceRequestHead(const KURL&);
 
@@ -486,6 +503,16 @@
     return allowHTTP1ForStreamingUpload_;
   }
 
+  const base::Optional<ResourceRequestHead::WebBundleTokenParams>&
+  GetWebBundleTokenParams() const {
+    return web_bundle_token_params_;
+  }
+
+  void SetWebBundleTokenParams(
+      ResourceRequestHead::WebBundleTokenParams params) {
+    web_bundle_token_params_ = params;
+  }
+
  private:
   const CacheControlHeader& GetCacheControlHeader() const;
 
@@ -581,6 +608,11 @@
   // prefetch responses. The browser process uses this token to ensure the
   // request is cached correctly.
   base::Optional<base::UnguessableToken> recursive_prefetch_token_;
+
+  // This is used when fetching either a WebBundle or a subresrouce in the
+  // WebBundle. The network process uses this token to associate the request to
+  // the bundle.
+  base::Optional<WebBundleTokenParams> web_bundle_token_params_;
 };
 
 class PLATFORM_EXPORT ResourceRequestBody {
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
index f8ec1ef..25086dd 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
@@ -303,6 +303,12 @@
   dest->credentials_mode = src.GetCredentialsMode();
   dest->redirect_mode = src.GetRedirectMode();
   dest->fetch_integrity = src.GetFetchIntegrity().Utf8();
+  if (src.GetWebBundleTokenParams().has_value()) {
+    dest->web_bundle_token_params =
+        base::make_optional(network::ResourceRequest::WebBundleTokenParams(
+            src.GetWebBundleTokenParams()->token,
+            src.GetWebBundleTokenParams()->CloneHandle()));
+  }
 
   mojom::ResourceType resource_type =
       RequestContextToResourceType(src.GetRequestContext());
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 14fc7d2..87698da 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -640,7 +640,7 @@
       // Support for ::target-text pseudo element as specified in
       // https://drafts.csswg.org/css-pseudo/#selectordef-target-text
       name: "CSSTargetTextPseudoElement",
-      status: "test",
+      status: "stable",
     },
     // Support for @property rules.
     {
@@ -1181,10 +1181,6 @@
       name: "MediaCaptureDepthVideoKind",
       status: "experimental",
     },
-    {
-      name: "MediaCapturePanTilt",
-      status: "stable",
-    },
     // Set to reflect the MediaCastOverlayButton feature.
     {
       name: "MediaCastOverlayButton",
@@ -1910,6 +1906,9 @@
       status: "test"
     },
     {
+      name: "SummaryListItem",
+    },
+    {
       name: "SurfaceEmbeddingFeatures",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
index 7eb9285..2e2d744e 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc
@@ -206,26 +206,11 @@
   delegate_->ApplyViewportChanges(args);
 }
 
-void LayerTreeView::RecordManipulationTypeCounts(cc::ManipulationInfo info) {
+void LayerTreeView::UpdateCompositorScrollState(
+    const cc::CompositorCommitData& commit_data) {
   if (!delegate_)
     return;
-  delegate_->RecordManipulationTypeCounts(info);
-}
-
-void LayerTreeView::SendOverscrollEventFromImplSide(
-    const gfx::Vector2dF& overscroll_delta,
-    cc::ElementId scroll_latched_element_id) {
-  if (!delegate_)
-    return;
-  delegate_->SendOverscrollEventFromImplSide(overscroll_delta,
-                                             scroll_latched_element_id);
-}
-
-void LayerTreeView::SendScrollEndEventFromImplSide(
-    cc::ElementId scroll_latched_element_id) {
-  if (!delegate_)
-    return;
-  delegate_->SendScrollEndEventFromImplSide(scroll_latched_element_id);
+  delegate_->UpdateCompositorScrollState(commit_data);
 }
 
 void LayerTreeView::RequestNewLayerTreeFrameSink() {
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
index 0f75599..53d47b2e 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view.h
@@ -79,12 +79,8 @@
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
-  void RecordManipulationTypeCounts(cc::ManipulationInfo info) override;
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override;
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override;
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override;
   void RequestNewLayerTreeFrameSink() override;
   void DidInitializeLayerTreeFrameSink() override;
   void DidFailToInitializeLayerTreeFrameSink() override;
diff --git a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
index a6e58099..d21c055a 100644
--- a/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
+++ b/third_party/blink/renderer/platform/widget/compositing/layer_tree_view_delegate.h
@@ -14,7 +14,6 @@
 namespace cc {
 class LayerTreeFrameSink;
 struct BeginMainFrameMetrics;
-struct ElementId;
 struct WebVitalMetrics;
 class RenderFrameMetadataObserver;
 }  // namespace cc
@@ -34,20 +33,8 @@
   virtual void ApplyViewportChanges(
       const cc::ApplyViewportChangesArgs& args) = 0;
 
-  // Record use counts of different methods of scrolling (e.g. wheel, touch,
-  // precision touchpad, etc.).
-  virtual void RecordManipulationTypeCounts(cc::ManipulationInfo info) = 0;
-
-  // Send overscroll DOM event when overscrolling has happened on the compositor
-  // thread.
-  virtual void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) = 0;
-
-  // Send scrollend DOM event when gesture scrolling on the compositor thread
-  // has finished.
-  virtual void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) = 0;
+  virtual void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) = 0;
 
   // Notifies that the compositor has issued a BeginMainFrame.
   virtual void BeginMainFrame(base::TimeTicks frame_time) = 0;
diff --git a/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h b/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
index d414e96..fb19920 100644
--- a/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
+++ b/third_party/blink/renderer/platform/widget/compositing/test/stub_layer_tree_view_delegate.h
@@ -23,12 +23,8 @@
       LayerTreeFrameSinkCallback callback) override {}
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
   }
-  void RecordManipulationTypeCounts(cc::ManipulationInfo info) override {}
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override {}
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override {}
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override {}
   void BeginMainFrame(base::TimeTicks frame_time) override {}
   void OnDeferMainFrameUpdatesChanged(bool) override {}
   void OnDeferCommitsChanged(bool) override {}
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 1f2e1654..2b882767 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -460,20 +460,9 @@
   client_->ApplyViewportChanges(args);
 }
 
-void WidgetBase::RecordManipulationTypeCounts(cc::ManipulationInfo info) {
-  client_->RecordManipulationTypeCounts(info);
-}
-
-void WidgetBase::SendOverscrollEventFromImplSide(
-    const gfx::Vector2dF& overscroll_delta,
-    cc::ElementId scroll_latched_element_id) {
-  client_->SendOverscrollEventFromImplSide(overscroll_delta,
-                                           scroll_latched_element_id);
-}
-
-void WidgetBase::SendScrollEndEventFromImplSide(
-    cc::ElementId scroll_latched_element_id) {
-  client_->SendScrollEndEventFromImplSide(scroll_latched_element_id);
+void WidgetBase::UpdateCompositorScrollState(
+    const cc::CompositorCommitData& commit_data) {
+  client_->UpdateCompositorScrollState(commit_data);
 }
 
 void WidgetBase::OnDeferMainFrameUpdatesChanged(bool defer) {
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index 836d29d..752a720d 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -124,12 +124,8 @@
   // Applies viewport related properties during a commit from the compositor
   // thread.
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
-  void RecordManipulationTypeCounts(cc::ManipulationInfo info) override;
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override;
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override;
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override;
   void BeginMainFrame(base::TimeTicks frame_time) override;
   void OnDeferMainFrameUpdatesChanged(bool) override;
   void OnDeferCommitsChanged(bool) override;
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h
index d81695c..51f1de9f 100644
--- a/third_party/blink/renderer/platform/widget/widget_base_client.h
+++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -95,13 +95,8 @@
   // thread.
   virtual void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) {}
 
-  virtual void RecordManipulationTypeCounts(cc::ManipulationInfo info) {}
-
-  virtual void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) {}
-  virtual void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) {}
+  virtual void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) {}
 
   virtual void DidBeginMainFrame() {}
   virtual void DidCommitAndDrawCompositorFrame() {}
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 68afdd8..4381b79 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -328,6 +328,8 @@
             'cc::kManipulationInfoPrecisionTouchPad',
             'cc::kManipulationInfoTouch',
             'cc::kManipulationInfoWheel',
+            'cc::kManipulationInfoScrollbar',
+            'cc::kManipulationInfoNone',
             'cc::kPixelsPerLineStep',
             'cc::kMinFractionToStepWhenPaging',
             'cc::kPercentDeltaForDirectionalScroll',
@@ -657,6 +659,7 @@
         'paths':
         ['third_party/blink/renderer/core/frame/web_frame_widget_impl.cc'],
         'allowed': [
+            'cc::CompositorCommitData',
             'cc::InputHandlerScrollResult',
             'cc::SwapPromise',
             'viz::CompositorFrameMetadata',
@@ -664,6 +667,13 @@
     },
     {
         'paths':
+        ['third_party/blink/renderer/core/frame/web_frame_widget_impl.h'],
+        'allowed': [
+            'cc::CompositorCommitData',
+        ],
+    },
+    {
+        'paths':
         ['third_party/blink/renderer/core/frame/web_local_frame_impl.cc'],
         'allowed': [
             'ui::AXTreeID',
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 4c3ba07b..efced38 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -420,6 +420,7 @@
 crbug.com/591099 fast/writing-mode/flipped-blocks-inline-map-local-to-container.html [ Failure ]
 
 ### external/wpt/html/rendering
+crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-overflow-hidden.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-overflow.html [ Crash Failure ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-vertical.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 39d54b4..3f863e8 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1006,6 +1006,7 @@
 virtual/layout_ng_block_frag/external/wpt/css/css-break/tall-line-in-short-fragmentainer-002.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/trailing-child-margin-000.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/trailing-child-margin-002.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-contain/contain-size-monolithic-002.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-multicol/balance-break-avoidance-001.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-multicol/balance-orphans-widows-000.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-multicol/baseline-001.html [ Pass ]
@@ -2957,7 +2958,6 @@
 crbug.com/626703 external/wpt/css/selectors/old-tests/css3-modsel-18b.xml [ Skip ]
 crbug.com/626703 external/wpt/css/selectors/old-tests/css3-modsel-159.xml [ Skip ]
 crbug.com/626703 external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.html [ Timeout ]
-crbug.com/626703 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html [ Failure ]
 crbug.com/626703 external/wpt/quirks/text-decoration-doesnt-propagate-into-tables/quirks.html [ Failure ]
 crbug.com/626703 external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-painting-order.html [ Failure ]
 crbug.com/626703 external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.html [ Timeout ]
@@ -5921,3 +5921,6 @@
 # Sheriff 2020-12-04
 # Failing on Webkit Linux Leak
 crbug.com/1155771 [ Linux ] external/wpt/dom/events/AddEventListenerOptions-signal.any.html [ Pass Failure ]
+
+# DevTools roll
+crbug.com/1144171 http/tests/devtools/report-API-errors.js [ Skip ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 0d46d96..e9ed2cd 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -179942,7 +179942,7 @@
      []
     ],
     "README.md": [
-     "959bab67d65d04c302cc372ce03e908c876e038e",
+     "fc7c2a230ea55e481a4d6869a9636889409d9818",
      []
     ],
     "build-css-testsuites.sh": [
@@ -199220,7 +199220,7 @@
        []
       ],
       "column-widths-expected.txt": [
-       "79fcbabb4c279d0fdb015d73e95542cf111e03d9",
+       "b44c757e9bcd46a435d11c41483b60fd1ddc3002",
        []
       ],
       "support": {
@@ -232802,10 +232802,6 @@
       "60c415f18633e2bf36d2b2e052ca8ab18493debb",
       []
      ],
-     "resolving.https-expected.txt": [
-      "cdab62db6e169eb62318b8d35c4fd8fb68a66c71",
-      []
-     ],
      "resources": {
       "data-base-url.json": [
        "81fcf087425c70eefa7d3c3465be59684fb1a521",
@@ -255427,6 +255423,10 @@
     ]
    },
    "webmessaging": {
+    "DIR_METADATA": [
+     "d83a533338ff449a0f33999a3da0af21d66a18f4",
+     []
+    ],
     "META.yml": [
      "95d5071171b5a20cc14a414c97c9eae2f525f43f",
      []
@@ -255436,7 +255436,7 @@
      []
     ],
     "OWNERS": [
-     "9f8c4a0f1f083d91fd866f103ab146cbef8bb149",
+     "1b10b3436d923b017c68c642cc7a95a5dc331193",
      []
     ],
     "README.md": [
@@ -255545,12 +255545,16 @@
     ]
    },
    "webrtc": {
+    "DIR_METADATA": [
+     "d9367eacac10dc9d10f681cea242ac8303374e04",
+     []
+    ],
     "META.yml": [
      "eed4ac834313773528e5665d80505cb4b951a1c9",
      []
     ],
     "OWNERS": [
-     "082fd71fff8db12a5261256034196899b876410b",
+     "e6ed2ffc906bb65b2656aaa4c26667d5454a98ca",
      []
     ],
     "README.md": [
@@ -255947,12 +255951,12 @@
      "326368358efeaed3fb51ac2d8595b56b93743aef",
      []
     ],
-    "META.yml": [
-     "14e23e03a854f4d83caa345d45c5b43f1a68bee3",
+    "DIR_METADATA": [
+     "60fd6e9478ce335431f3f4b1791f3cbdd905b812",
      []
     ],
-    "OWNERS": [
-     "09bdfc47235fcb66b6542574f3deaeb630ddf869",
+    "META.yml": [
+     "14e23e03a854f4d83caa345d45c5b43f1a68bee3",
      []
     ],
     "README.md": [
@@ -256223,12 +256227,16 @@
     ]
    },
    "webstorage": {
+    "DIR_METADATA": [
+     "f2b243d60e7018a6a5cdefa6b13b61c6b2cfc62a",
+     []
+    ],
     "META.yml": [
      "020075c3f2401ca4b8da73116a84582d46e265c0",
      []
     ],
     "OWNERS": [
-     "5f5f8d3217e2ee4e99d6413b62fdc541ecfeef99",
+     "087586107f86d394c1d144322ca8be78fe5ecdaa",
      []
     ],
     "README.md": [
@@ -256349,12 +256357,16 @@
     }
    },
    "webusb": {
+    "DIR_METADATA": [
+     "d8d7bf94433b6b7eb5de303b61da3ccb8d07cc22",
+     []
+    ],
     "META.yml": [
      "546094855e50f17ef6d92f4bec412af644f155ad",
      []
     ],
     "OWNERS": [
-     "8dc3166e8c922b91d88520858792c9b63d08111b",
+     "acf1a9ed5530a468ec9cd36e973be49a9ed47f10",
      []
     ],
     "README.md": [
@@ -256413,12 +256425,16 @@
     ]
    },
    "webvtt": {
+    "DIR_METADATA": [
+     "cbab05cb6b7a23a03fa8129f0b82005d1f414912",
+     []
+    ],
     "META.yml": [
      "949f312d7acc3444b3c116a55d1175dd12be33ca",
      []
     ],
     "OWNERS": [
-     "7be00e4ae7fb31e3cf3c1b61ea9386d3a9698680",
+     "7e9f03ee201185ccf2862e103dbdd316822f0eb0",
      []
     ],
     "README.md": [
@@ -258399,12 +258415,16 @@
     }
    },
    "webxr": {
+    "DIR_METADATA": [
+     "4d83ec231da8ac40b41bf76b06d1eebcfabaa2fa",
+     []
+    ],
     "META.yml": [
      "520afec2000721d04aae9ed89bca49297fa31d4b",
      []
     ],
     "OWNERS": [
-     "cb7f47252d5746f61f1c9df2d981d47ef35940c5",
+     "95884464ed9f1f9016ef219e58c200c3d193cd8c",
      []
     ],
     "dom-overlay": {
@@ -258465,12 +258485,12 @@
     ]
    },
    "workers": {
-    "META.yml": [
-     "a7297d3844728b8bb2f7c82a2c4f32d65040a919",
+    "DIR_METADATA": [
+     "1201c657c66c2be674ccc86efcfc2b60f69da7c8",
      []
     ],
-    "OWNERS": [
-     "51dd60fea8510b6c27acaa22bfee6924191fd3e2",
+    "META.yml": [
+     "a7297d3844728b8bb2f7c82a2c4f32d65040a919",
      []
     ],
     "README.md": [
@@ -259673,12 +259693,12 @@
     }
    },
    "worklets": {
-    "META.yml": [
-     "5c92c43c538bba5cc8e9ac14bd5bff1716c5ae4d",
+    "DIR_METADATA": [
+     "1201c657c66c2be674ccc86efcfc2b60f69da7c8",
      []
     ],
-    "OWNERS": [
-     "51dd60fea8510b6c27acaa22bfee6924191fd3e2",
+    "META.yml": [
+     "5c92c43c538bba5cc8e9ac14bd5bff1716c5ae4d",
      []
     ],
     "README.md": [
@@ -259853,12 +259873,16 @@
     []
    ],
    "x-frame-options": {
+    "DIR_METADATA": [
+     "b0803f15b31aaabcf754db9be495c18fcfc0d586",
+     []
+    ],
     "META.yml": [
      "21ef0699364f406f337cfda70d974bacc4b5a934",
      []
     ],
     "OWNERS": [
-     "243f594fd111921c2ed11265c4367ae0e4f526a7",
+     "3f8456354bd08a3cb87e767ca0eb508603f45f1b",
      []
     ],
     "README.md": [
@@ -259885,12 +259909,12 @@
     }
    },
    "xhr": {
-    "META.yml": [
-     "c343ceac4375531d92d2ef1ec88de5c7a3897bbe",
+    "DIR_METADATA": [
+     "89a667c20874991a44ac19bdb09052614c8eb7b8",
      []
     ],
-    "OWNERS": [
-     "d9e80484a4ef2e0bb4a655968c24b06e97e180ac",
+    "META.yml": [
+     "c343ceac4375531d92d2ef1ec88de5c7a3897bbe",
      []
     ],
     "README.md": [
@@ -299971,7 +299995,7 @@
        ]
       ],
       "column-widths.html": [
-       "52cb5b6fb0502cd63e2404168cfd413d86d60b6e",
+       "b151c2263bb625c0ec4bd2e2159b7bf15602fc63",
        [
         null,
         {}
@@ -413905,7 +413929,7 @@
       ]
      ],
      "general.any.js": [
-      "8ae6af70efd6c9ba08b8affdfafb7b74307072e1",
+      "bbcae335b11cb51dbf3e6581ee692d459ff1c826",
       [
        null,
        {
diff --git a/third_party/blink/web_tests/external/wpt/css/README.md b/third_party/blink/web_tests/external/wpt/css/README.md
index 959bab6..fc7c2a2 100644
--- a/third_party/blink/web_tests/external/wpt/css/README.md
+++ b/third_party/blink/web_tests/external/wpt/css/README.md
@@ -25,11 +25,11 @@
 vendor-imports/ Directory
 -------------------------
 
-vendor-imports/ is a legacy directory where third parties historically imported
-their tests that originate and are maintained in an external repo. Files in
-this directory should never be modified in this repo, but should go through the
-vendor's process to be imported here.
-
+vendor-imports/ is the remains of a legacy directory where third parties
+historically imported their tests. These tests should be moved into the correct
+directory under css/, see
+[#8615](https://github.com/web-platform-tests/wpt/issues/8615). In the meantime,
+feel free to fix any test bugs in the usual way.
 
 Importing Old Branches
 ----------------------
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block-ref.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block-ref.html
index ccb755b..282c0d4 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block-ref.html
@@ -2,7 +2,9 @@
 <title>Reference for fieldset containing block</title>
 <style>
 p { margin: 0; height: 100px }
-div { position: absolute; top: 108px; width: 100px; height: 100px; background: lime; }
+.div1 { position: absolute; top: 108px; width: 100px; height: 100px; background: lime; }
+.div2 { position: absolute; top: 158px; width: 200px; height: 100px; background: lime; }
 </style>
 <p>There should be no red.</p>
-<div></div>
+<div class="div1"></div>
+<div class="div2"></div>
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html
index e7529946..cefb358 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/fieldset-containing-block.html
@@ -7,7 +7,29 @@
 legend { padding: 0; width: 100px; height: 50px; background: lime; }
 div { position: absolute; top: 0; width: 100px; height: 50px; background: lime; }
 .behind { height:100px; top: 108px; background: red; }
+
+.fixed-container {
+  filter: invert();
+  overflow: hidden;
+  width: 200px;
+  height: 200px;
+}
+.fixed {
+  position: fixed;
+  width: 400px;
+  height: 100px;
+  background: linear-gradient(to right, #ff00ff 50%, #00ffff 50%);
+}
+.has-fixed {
+  width: 0px;
+  height: 0px;
+}
 </style>
 <p>There should be no red.</p>
 <div class="behind"></div>
 <fieldset><legend></legend><div></div></fieldset>
+
+<fieldset class="fixed-container">
+<legend class="has-fixed"><div style="position:fixed; width:0; height0;"></div></legend>
+<div class="fixed"></div>
+</fieldset>
diff --git a/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/general.any.js b/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/general.any.js
index 8ae6af7..bbcae335b 100644
--- a/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/general.any.js
+++ b/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/general.any.js
@@ -108,6 +108,11 @@
 
 }, 'ReadableStream with byte source: No automatic pull call if start doesn\'t finish');
 
+test(() => {
+  assert_throws_js(Error, () => new ReadableStream({ start() { throw new Error(); }, type:'bytes' }),
+      'start() can throw an exception with type: bytes');
+}, 'ReadableStream with byte source: start() throws an exception');
+
 promise_test(t => {
   new ReadableStream({
     pull: t.unreached_func('pull() should not be called'),
@@ -2056,6 +2061,11 @@
 }, 'ReadableStream with byte source: default reader + autoAllocateChunkSize + byobRequest interaction');
 
 test(() => {
+  assert_throws_js(TypeError, () => new ReadableStream({ autoAllocateChunkSize: 0, type: 'bytes' }),
+      'controller cannot be setup with autoAllocateChunkSize = 0');
+}, 'ReadableStream with byte source: autoAllocateChunkSize cannot be 0');
+
+test(() => {
   const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
   const stream = new ReadableStream({ type: 'bytes' });
   new ReadableStreamBYOBReader(stream);
diff --git a/third_party/blink/web_tests/fast/css/usecounter-summary-display-block.html b/third_party/blink/web_tests/fast/css/usecounter-summary-display-block.html
deleted file mode 100644
index c96b87fd..0000000
--- a/third_party/blink/web_tests/fast/css/usecounter-summary-display-block.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE html>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<style>
-summary.first {
-    display: list-item;
-}
-
-summary.second {
-    display: flex;
-}
-
-summary.third {
-    display: some-invalid-value;
-}
-
-summary.fourth {
-    display: block;
-}
-</style>
-<details><summary>A</summary>B</details>
-<script>
-'use strict';
-
-test(() => {
-    let SummaryElementWithDisplayBlockAuthorRule = 1434; // From UseCounter.h
-    let isCounted = () => internals.isUseCounted(document, SummaryElementWithDisplayBlockAuthorRule);
-    assert_false(isCounted(),
-                 'user agent stylesheet rules should not be counted');
-
-    let summary = document.querySelector('summary');
-    assert_equals(
-        'block', window.getComputedStyle(summary).display,
-        'the user agent stylsheet display property should be in effect');
-    assert_false(
-        isCounted(),
-        'user agent stylesheet rules should not be counted');
-
-    summary.classList.add('first');
-    assert_equals(
-        'block', window.getComputedStyle(summary).display,
-        'the summary is prevented from generating a ::marker since it already has a disclosure symbol');
-    assert_false(
-        isCounted(),
-        'the author did not specify `display: block` so it should not not be counted');
-
-    summary.classList.remove('first');
-    summary.classList.add('second');
-    assert_equals(
-        'flex', window.getComputedStyle(summary).display,
-        'the author stylesheet display property should be in effect');
-    assert_false(
-        isCounted(),
-        'valid values other than block should not be counted');
-
-    summary.classList.remove('second');
-    summary.classList.add('third');
-    assert_equals(
-        'block', window.getComputedStyle(summary).display,
-        'the user agent stylesheet display property should be in effect ' +
-        'when the author stylesheet value is invalid');
-    assert_false(
-        isCounted(),
-        'only values from an author rule should be counted');
-
-    summary.classList.remove('third');
-    summary.classList.add('fourth');
-    assert_equals(
-        'block', window.getComputedStyle(summary).display,
-        'the author stylesheet display property should be in effect');
-    assert_true(
-        isCounted(),
-        'author rules for display: block summary should tickle the counter');
-}, 'summary with author display: block is use counted');
-</script>
diff --git a/third_party/blink/web_tests/fast/lists/ol-display-types.html b/third_party/blink/web_tests/fast/lists/ol-display-types.html
index b63d873..769f79b 100644
--- a/third_party/blink/web_tests/fast/lists/ol-display-types.html
+++ b/third_party/blink/web_tests/fast/lists/ol-display-types.html
@@ -1,9 +1,18 @@
 <html>
 <head>
 <title>OL Display Types LayoutTest</title>
+<style>
+.inside {
+  list-style-position: inside;
+  background: #cff;
+  padding: 0;
+}
+</style>
 </head>
 <body>
 <p>Test of various CSS display types for list elements. All visible elements that have a display-type of list-item are given a number. This is generally going to be LI element.</p>
+<table border="0">
+<tr><td>
 <ol start="3">
 <li>Should be 3</li>
 <li style="display: none;">Invisible and has no item</li>
@@ -26,5 +35,30 @@
 <li style="list-style-type: disclosure-open;">Should have a triangle</li>
 <li style="list-style-type: disclosure-closed;">Should have another triangle</li>
 </ol>
+
+<td>
+<ol start="3" class="inside">
+<li>Should be 3</li>
+<li style="display: none;">Invisible and has no item</li>
+<li>Should be 4</li>
+<li style="display: block;">Should not have a number</li>
+<li>Should be 5</li>
+<li style="display: inline;">Should not have a number</li>
+<li>Should be 6</li>
+<div style="display: block;">Should not have a number</div>
+<div style="display: list-item;">Should be 7</div>
+<li>Should be 8</li>
+<li style="list-style-type: none;">Should not have a number</li>
+<li>Should be 10</li>
+<li style="list-style-type: disc;">Should have a disc</li>
+<li>Should be 12</li>
+<li style="list-style-type: square;">Should have a square</li>
+<li>Should be 14</li>
+<li style="list-style-type: circle;">Should have a circle</li>
+<li>Should be 16</li>
+<li style="list-style-type: disclosure-open;">Should have a triangle</li>
+<li style="list-style-type: disclosure-closed;">Should have another triangle</li>
+</ol>
+</tr></table>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/lists/ol-display-types-expected.png b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/lists/ol-display-types-expected.png
index 78b23d0..730918fe 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/lists/ol-display-types-expected.png
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/lists/ol-display-types-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-1-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-1-and-click.html
deleted file mode 100644
index def40a8..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-1-and-click.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-1.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-1.html
deleted file mode 100644
index b3d8786..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-1.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-10-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-10-and-click.html
deleted file mode 100644
index 49e82d0..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-10-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new1", "new 1"), document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-10.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-10.html
deleted file mode 100644
index ace1d76..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-10.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new1", "new 1"), document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-2-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-2-and-click.html
deleted file mode 100644
index 40ca5fe9..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-2-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new2", "new 2"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1"></details>
-</body>
-
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-2.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-2.html
deleted file mode 100644
index b8ab7da8..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-2.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new2", "new 2"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1"></details>
-</body>
-
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-3-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-3-and-click.html
deleted file mode 100644
index 98230c8..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-3-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new2", "new 2"), document.getElementById("new1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-3.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-3.html
deleted file mode 100644
index b92e294..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-3.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new2", "new 2"), document.getElementById("new1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-4-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-4-and-click.html
deleted file mode 100644
index 72e19cc..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-4-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-4.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-4.html
deleted file mode 100644
index 3a075d90..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-4.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-5-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-5-and-click.html
deleted file mode 100644
index 0eb1c02..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-5-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new1", "new 1"), document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-5.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-5.html
deleted file mode 100644
index f7f58754..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-5.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new1", "new 1"), document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-6-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-6-and-click.html
deleted file mode 100644
index a9f9da9f..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-6-and-click.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-6.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-6.html
deleted file mode 100644
index a8c95d0..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-6.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-7-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-7-and-click.html
deleted file mode 100644
index a64648ca..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-7-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new2", "new 2"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1"></details>
-</body>
-
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-7.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-7.html
deleted file mode 100644
index d75f378..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-7.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new2", "new 2"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1"></details>
-</body>
-
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-8-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-8-and-click.html
deleted file mode 100644
index 07843e9..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-8-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new2", "new 2"), document.getElementById("new1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-8.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-8.html
deleted file mode 100644
index e7f04add..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-8.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-    document.getElementById("dt1").insertBefore(createNewElement("summary", "new2", "new 2"), document.getElementById("new1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1"></details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-9-and-click.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-9-and-click.html
deleted file mode 100644
index f19358a..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-9-and-click.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-9.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-9.html
deleted file mode 100644
index 20b97e2e..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-add-summary-9.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var createNewElement = function (tag, id, text) {
-    var result = document.createElement(tag);
-    result.setAttribute('id',id);
-    result.innerHTML = text;
-    return result;
-};
-
-var runTests = function () {
-    document.getElementById("dt1").appendChild(createNewElement("summary", "new1", "new 1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary-expected.html b/third_party/blink/web_tests/html/details_summary/details-add-summary-expected.html
new file mode 100644
index 0000000..e43984e2
--- /dev/null
+++ b/third_party/blink/web_tests/html/details_summary/details-add-summary-expected.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<body>
+<details id="dt1"><summary>new 1</summary></details>
+<details id="dt1c" open><summary>new 1</summary></details>
+<details id="dt2"><summary>new 1</summary><summary>new 2</summary></details>
+<details id="dt2c" open><summary>new 1</summary><summary>new 2</summary></details>
+<details id="dt3"><summary>new 2</summary><summary>new 1</summary></details>
+<details id="dt3c" open><summary>new 2</summary><summary>new 1</summary></details>
+<details id="dt4"><summary>summary</summary><summary>new 1</summary></details>
+<details id="dt4c" open><summary>summary</summary><summary>new 1</summary></details>
+<details id="dt5"><summary>new 1</summary><summary>summary</summary></details>
+<details id="dt5c" open><summary>new 1</summary><summary>summary</summary></details>
+<details id="dt6" open><summary>new 1</summary></details>
+<details id="dt6c"><summary>new 1</summary></details>
+<details id="dt7" open><summary>new 1</summary><summary>new 2</summary></details>
+<details id="dt7c"><summary>new 1</summary><summary>new 2</summary></details>
+<details id="dt8" open><summary>new 2</summary><summary>new 1</summary></details>
+<details id="dt8c"><summary>new 2</summary><summary>new 1</summary></details>
+<details id="dt9" open><summary>summary</summary><summary>new 1</summary></details>
+<details id="dt9c"><summary>summary</summary><summary>new 1</summary></details>
+<details id="dt10" open><summary>new 1</summary><summary>summary</summary></details>
+<details id="dt10c"><summary>new 1</summary><summary>summary</summary></details>
+<script>
+document.querySelector('#dt10c summary').focus();
+</script>
+</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-add-summary.html b/third_party/blink/web_tests/html/details_summary/details-add-summary.html
new file mode 100644
index 0000000..2b604b78
--- /dev/null
+++ b/third_party/blink/web_tests/html/details_summary/details-add-summary.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<script>
+function createNewElement(tag, id, text) {
+  var result = document.createElement(tag);
+  if (id)
+    result.setAttribute('id', id);
+  result.innerHTML = text;
+  return result;
+}
+
+function clickOn(element, x, y) {
+  const rect = element.getBoundingClientRect();
+  return new Promise((resolve, reject) => {
+    chrome.gpuBenchmarking.pointerActionSequence([
+        {source: 'mouse',
+         actions: [
+             {name: 'pointerMove', x: rect.x + x, y: rect.y + y},
+             {name: 'pointerDown', x: rect.x + x, y: rect.y + y, button: 0},
+             {name: 'pointerUp', button: 0}]
+        }], resolve);
+  });
+}
+
+const $ = document.querySelector.bind(document);
+
+async function runTests() {
+  $('#dt1').appendChild(createNewElement('summary', null, 'new 1'));
+
+  $('#dt1c').appendChild(createNewElement("summary", null, "new 1"));
+  await clickOn($('#dt1c'), 2, 2);
+
+  $('#dt2').appendChild(createNewElement("summary", null, "new 1"));
+  $('#dt2').appendChild(createNewElement("summary", null, "new 2"));
+
+  $('#dt2c').appendChild(createNewElement("summary", null, "new 1"));
+  $('#dt2c').appendChild(createNewElement("summary", null, "new 2"));
+  await clickOn($('#dt2c'), 2, 2);
+
+  $('#dt3').appendChild(createNewElement("summary", "new3", "new 1"));
+  $('#dt3').insertBefore(createNewElement("summary", null, "new 2"), $('#new3'));
+
+  $('#dt3c').appendChild(createNewElement("summary", "new3c", "new 1"));
+  $('#dt3c').insertBefore(createNewElement("summary", null, "new 2"), $('#new3c'));
+  await clickOn($('#dt3c'), 2, 2);
+
+  $('#dt4').appendChild(createNewElement("summary", null, "new 1"));
+
+  $('#dt4c').appendChild(createNewElement("summary", null, "new 1"));
+  await clickOn($('#dt4c'), 2, 2);
+
+  $('#dt5').insertBefore(createNewElement("summary", null, "new 1"), $('#summary5'));
+
+  $('#dt5c').insertBefore(createNewElement("summary", null, "new 1"), $('#summary5c'));
+  await clickOn($('#dt5c'), 2, 2);
+
+  $('#dt6').appendChild(createNewElement("summary", null, "new 1"));
+
+  $('#dt6c').appendChild(createNewElement("summary", null, "new 1"));
+  await clickOn($('#dt6c'), 2, 2);
+
+  $('#dt7').appendChild(createNewElement("summary", null, "new 1"));
+  $('#dt7').appendChild(createNewElement("summary", null, "new 2"));
+
+  $('#dt7c').appendChild(createNewElement("summary", null, "new 1"));
+  $('#dt7c').appendChild(createNewElement("summary", null, "new 2"));
+  await clickOn($('#dt7c'), 2, 2);
+
+  $('#dt8').appendChild(createNewElement("summary", "new8", "new 1"));
+  $('#dt8').insertBefore(createNewElement("summary", null, "new 2"), $('#new8'));
+
+  $('#dt8c').appendChild(createNewElement("summary", "new8c", "new 1"));
+  $('#dt8c').insertBefore(createNewElement("summary", null, "new 2"), $('#new8c'));
+  await clickOn($('#dt8c'), 2, 2);
+
+  $('#dt9').appendChild(createNewElement("summary", null, "new 1"));
+
+  $('#dt9c').appendChild(createNewElement("summary", null, "new 1"));
+  await clickOn($('#dt9c'), 2, 2);
+
+  $('#dt10').insertBefore(createNewElement("summary", null, "new 1"), $('#summary10'));
+
+  $('#dt10c').insertBefore(createNewElement("summary", null, "new 1"), $('#summary10c'));
+  await clickOn($('#dt10c'), 2, 2);
+
+  testRunner.notifyDone();
+}
+
+testRunner.waitUntilDone();
+
+</script>
+<body onload="runTests()">
+<details id="dt1"></details>
+<details id="dt1c"></details>
+<details id="dt2"></details>
+<details id="dt2c"></details>
+<details id="dt3"></details>
+<details id="dt3c"></details>
+<details id="dt4"><summary>summary</summary></details>
+<details id="dt4c"><summary>summary</summary></details>
+<details id="dt5"><summary id="summary5">summary</summary></details>
+<details id="dt5c"><summary id="summary5c">summary</summary></details>
+<details id="dt6" open></details>
+<details id="dt6c" open></details>
+<details id="dt7" open></details>
+<details id="dt7c" open></details>
+<details id="dt8" open></details>
+<details id="dt8c" open></details>
+<details id="dt9" open><summary>summary</summary></details>
+<details id="dt9c" open><summary>summary</summary></details>
+<details id="dt10" open><summary id="summary10">summary</summary></details>
+<details id="dt10c" open><summary id="summary10c">summary</summary></details>
+</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-1-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-1-and-click.html
deleted file mode 100644
index 3925100..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-1-and-click.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-1.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-1.html
deleted file mode 100644
index df2805b7..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-1.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-2-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-2-and-click.html
deleted file mode 100644
index d35db84..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-2-and-click.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-2.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-2.html
deleted file mode 100644
index 3547891..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-2.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-3-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-3-and-click.html
deleted file mode 100644
index b37aca1..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-3-and-click.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary2"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-3.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-3.html
deleted file mode 100644
index d3a829bf..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-3.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary2"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-4-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-4-and-click.html
deleted file mode 100644
index 1fe58697..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-4-and-click.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-4.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-4.html
deleted file mode 100644
index 923fd97..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-4.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-5-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-5-and-click.html
deleted file mode 100644
index e15a108..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-5-and-click.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-5.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-5.html
deleted file mode 100644
index f10e52a..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-5.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary1"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-6-and-click.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-6-and-click.html
deleted file mode 100644
index b26883b..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-6-and-click.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary2"));
-
-    if (!window.testRunner || !window.eventSender)
-        return;
-    eventSender.mouseMoveTo(2, 2);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-};
-
-</script>
-
-<body style="margin: 0px" onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-6.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-6.html
deleted file mode 100644
index d132d04..0000000
--- a/third_party/blink/web_tests/html/details_summary/details-remove-summary-6.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<script>
-
-var runTests = function () {
-    document.getElementById("dt1").removeChild(document.getElementById("summary2"));
-};
-
-</script>
-
-<body onload="runTests()">
-    <details open id="dt1">
-        <summary id="summary1">summary 1</summary>
-        <summary id="summary2">summary 2</summary>
-    </details>
-</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary-expected.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary-expected.html
new file mode 100644
index 0000000..d18d029
--- /dev/null
+++ b/third_party/blink/web_tests/html/details_summary/details-remove-summary-expected.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<body>
+<details id="dt1"></details>
+<details id="dt1c" open></details>
+<details id="dt2"><summary>summary 2</summary></details>
+<details id="dt2c" open><summary>summary 2</summary></details>
+<details id="dt3"><summary>summary 1</summary></details>
+<details id="dt3c" open><summary>summary 1</summary></details>
+<details id="dt4" open></details>
+<details id="dt4c"></details>
+<details id="dt5" open><summary>summary 2</summary></details>
+<details id="dt5c"><summary>summary 2</summary></details>
+<details id="dt6" open><summary>summary 1</summary></details>
+<details id="dt6c"><summary>summary 1</summary></details>
+<script>
+document.querySelector('#dt6c summary').focus();
+</script>
+</body>
diff --git a/third_party/blink/web_tests/html/details_summary/details-remove-summary.html b/third_party/blink/web_tests/html/details_summary/details-remove-summary.html
new file mode 100644
index 0000000..9d58ae8
--- /dev/null
+++ b/third_party/blink/web_tests/html/details_summary/details-remove-summary.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<script>
+function clickOn(element, x, y) {
+  const rect = element.getBoundingClientRect();
+  return new Promise((resolve, reject) => {
+    chrome.gpuBenchmarking.pointerActionSequence([
+        {source: 'mouse',
+         actions: [
+             {name: 'pointerMove', x: rect.x + x, y: rect.y + y},
+             {name: 'pointerDown', x: rect.x + x, y: rect.y + y, button: 0},
+             {name: 'pointerUp', button: 0}]
+        }], resolve);
+  });
+}
+
+const $ = document.querySelector.bind(document);
+
+async function runTests() {
+  $('#dt1').removeChild($('#dt1 > summary'));
+
+  $('#dt1c').removeChild($('#dt1c > summary'));
+  await clickOn($('#dt1c'), 2, 2);
+
+  $('#dt2').removeChild($('#dt2 > summary'));
+
+  $('#dt2c').removeChild($('#dt2c > summary'));
+  await clickOn($('#dt2c'), 2, 2);
+
+  $('#dt3').removeChild($('#dt3 > summary:last-of-type'));
+
+  $('#dt3c').removeChild($('#dt3c > summary:last-of-type'));
+  await clickOn($('#dt3c'), 2, 2);
+
+  $('#dt4').removeChild($('#dt4 > summary'));
+
+  $('#dt4c').removeChild($('#dt4c > summary'));
+  await clickOn($('#dt4c'), 2, 2);
+
+  $('#dt5').removeChild($('#dt5 > summary'));
+
+  $('#dt5c').removeChild($('#dt5c > summary'));
+  await clickOn($('#dt5c'), 2, 2);
+
+  $('#dt6').removeChild($('#dt6 > summary:last-of-type'));
+
+  $('#dt6c').removeChild($('#dt6c > summary:last-of-type'));
+  await clickOn($('#dt6c'), 2, 2);
+
+  testRunner.notifyDone();
+}
+
+testRunner.waitUntilDone();
+
+</script>
+<body onload="runTests()">
+<details id="dt1"><summary>summary</summary></details>
+<details id="dt1c"><summary>summary</summary></details>
+<details id="dt2"><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt2c"><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt3"><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt3c"><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt4" open><summary>summary</summary></details>
+<details id="dt4c" open><summary>summary</summary></details>
+<details id="dt5" open><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt5c" open><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt6" open><summary>summary 1</summary><summary>summary 2</summary></details>
+<details id="dt6c" open><summary>summary 1</summary><summary>summary 2</summary></details>
+</body>
diff --git a/third_party/blink/web_tests/platform/linux/fast/lists/ol-display-types-expected.png b/third_party/blink/web_tests/platform/linux/fast/lists/ol-display-types-expected.png
index e7e2f810..f77a09f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/lists/ol-display-types-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/lists/ol-display-types-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-and-click-expected.png
deleted file mode 100644
index f45d7fb..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-expected.png
deleted file mode 100644
index e8d27e0..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-and-click-expected.png
deleted file mode 100644
index 35222a8..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-expected.png
deleted file mode 100644
index afa24d11..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-10-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-and-click-expected.png
deleted file mode 100644
index c15fc31..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-expected.png
deleted file mode 100644
index e8d27e0..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-and-click-expected.png
deleted file mode 100644
index 77c04f9..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-expected.png
deleted file mode 100644
index 79adf1d..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-and-click-expected.png
deleted file mode 100644
index 8353760a..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-expected.png
deleted file mode 100644
index cf40718..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-and-click-expected.png
deleted file mode 100644
index f3699d6..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-expected.png
deleted file mode 100644
index e8d27e0..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-and-click-expected.png
deleted file mode 100644
index 35222a8..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-expected.png
deleted file mode 100644
index becc6686..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-and-click-expected.png
deleted file mode 100644
index 35222a8..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-expected.png
deleted file mode 100644
index e64c0021..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-7-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-and-click-expected.png
deleted file mode 100644
index 9e084c7..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-expected.png
deleted file mode 100644
index 28632b9..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-8-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-and-click-expected.png
deleted file mode 100644
index 7d65c180..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-expected.png
deleted file mode 100644
index d7b9ce4..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-add-summary-9-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-and-click-expected.png
deleted file mode 100644
index 358d84c..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-expected.png
deleted file mode 100644
index 7054cf4..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-and-click-expected.png
deleted file mode 100644
index 88fb71b..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-expected.png
deleted file mode 100644
index efe5a1b..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-and-click-expected.png
deleted file mode 100644
index b30847f..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-expected.png
deleted file mode 100644
index aaaa711..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-and-click-expected.png
deleted file mode 100644
index 2a25f58..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-expected.png
deleted file mode 100644
index 9edd1c9b..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-and-click-expected.png
deleted file mode 100644
index d3bdec4c..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-expected.png
deleted file mode 100644
index 4248bb8..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-and-click-expected.png
deleted file mode 100644
index b79c81a..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-expected.png b/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-expected.png
deleted file mode 100644
index 6b17cf4..0000000
--- a/third_party/blink/web_tests/platform/linux/html/details_summary/details-remove-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/lists/ol-display-types-expected.png b/third_party/blink/web_tests/platform/mac/fast/lists/ol-display-types-expected.png
index 5c06b07..8e6f6d3 100644
--- a/third_party/blink/web_tests/platform/mac/fast/lists/ol-display-types-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/lists/ol-display-types-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-and-click-expected.png
deleted file mode 100644
index 6f5b21ef..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-expected.png
deleted file mode 100644
index 3abd423..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-and-click-expected.png
deleted file mode 100644
index 632a04b6..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-expected.png
deleted file mode 100644
index 2a0d5a13..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-10-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-and-click-expected.png
deleted file mode 100644
index 7fb98033..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-expected.png
deleted file mode 100644
index 3abd423..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-and-click-expected.png
deleted file mode 100644
index dedfc7d..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-expected.png
deleted file mode 100644
index b29318b..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-and-click-expected.png
deleted file mode 100644
index 73766a5..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-expected.png
deleted file mode 100644
index 5042354..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-and-click-expected.png
deleted file mode 100644
index 12f40610..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-expected.png
deleted file mode 100644
index 3abd423..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-and-click-expected.png
deleted file mode 100644
index 632a04b6..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-expected.png
deleted file mode 100644
index 9ec2a00..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-and-click-expected.png
deleted file mode 100644
index 632a04b6..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-expected.png
deleted file mode 100644
index cd1725b..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-7-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-and-click-expected.png
deleted file mode 100644
index 80cc312c..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-expected.png
deleted file mode 100644
index 4fa2895..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-8-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-and-click-expected.png
deleted file mode 100644
index 435885f..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-expected.png
deleted file mode 100644
index cb01e18..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-add-summary-9-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-and-click-expected.png
deleted file mode 100644
index e64c030..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-expected.png
deleted file mode 100644
index e22cd538..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-and-click-expected.png
deleted file mode 100644
index f6139448..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-expected.png
deleted file mode 100644
index 0333cc6..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-and-click-expected.png
deleted file mode 100644
index 7c2db47..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-expected.png
deleted file mode 100644
index 1db9a7f..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-and-click-expected.png
deleted file mode 100644
index 4a74fdf..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-expected.png
deleted file mode 100644
index 40d0545..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-and-click-expected.png
deleted file mode 100644
index cb1ccd7..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-expected.png
deleted file mode 100644
index 51796ffb..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-and-click-expected.png
deleted file mode 100644
index 7d1c03c..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-expected.png b/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-expected.png
deleted file mode 100644
index a78acc22..0000000
--- a/third_party/blink/web_tests/platform/mac/html/details_summary/details-remove-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/lists/ol-display-types-expected.png b/third_party/blink/web_tests/platform/win/fast/lists/ol-display-types-expected.png
index 63a4313..92109df 100644
--- a/third_party/blink/web_tests/platform/win/fast/lists/ol-display-types-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/lists/ol-display-types-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-and-click-expected.png
deleted file mode 100644
index 73bbd9f2..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-expected.png
deleted file mode 100644
index fc789d50..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-and-click-expected.png
deleted file mode 100644
index 1dce990..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-expected.png
deleted file mode 100644
index 9466d61..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-10-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-and-click-expected.png
deleted file mode 100644
index 25e150ed..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-expected.png
deleted file mode 100644
index fc789d50..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-and-click-expected.png
deleted file mode 100644
index 1f08ccb..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-expected.png
deleted file mode 100644
index e76cec3c..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-and-click-expected.png
deleted file mode 100644
index e7cac614..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-expected.png
deleted file mode 100644
index 404827a..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-and-click-expected.png
deleted file mode 100644
index 4beb78f..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-expected.png
deleted file mode 100644
index fc789d50..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-and-click-expected.png
deleted file mode 100644
index 1dce990..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-expected.png
deleted file mode 100644
index 6de88c2..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-and-click-expected.png
deleted file mode 100644
index 1dce990..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-expected.png
deleted file mode 100644
index 711176fe..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-7-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-and-click-expected.png
deleted file mode 100644
index dbdd4c74..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-expected.png
deleted file mode 100644
index e16b9b4..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-8-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-and-click-expected.png
deleted file mode 100644
index fec28cba..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-expected.png
deleted file mode 100644
index 2a677d10..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-add-summary-9-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-and-click-expected.png
deleted file mode 100644
index 771a7e4..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-expected.png
deleted file mode 100644
index 776022f..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-1-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-and-click-expected.png
deleted file mode 100644
index 76a7661..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-expected.png
deleted file mode 100644
index 5f28e11d..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-2-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-and-click-expected.png
deleted file mode 100644
index 98e2e37..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-expected.png
deleted file mode 100644
index fe7a572..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-3-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-and-click-expected.png
deleted file mode 100644
index 0ce36680..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-expected.png
deleted file mode 100644
index d4c9d21c..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-4-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-and-click-expected.png
deleted file mode 100644
index a4b89872..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-expected.png
deleted file mode 100644
index 7e5a077..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-5-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-and-click-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-and-click-expected.png
deleted file mode 100644
index 1274063..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-and-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-expected.png b/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-expected.png
deleted file mode 100644
index 9b0ac89..0000000
--- a/third_party/blink/web_tests/platform/win/html/details_summary/details-remove-summary-6-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 1bf55f6..64313ee5 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-4-19-g8cc4d0dc3
-Revision: 8cc4d0dc32a1f8734e77266bb6abdd11f18a631c
+Version: VER-2-10-4-42-g9f94d8533
+Revision: 9f94d8533cefa8a023a0b81633032fe0aaea08fb
 CPEPrefix: cpe:/a:freetype:freetype:2.10.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h b/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
index 4b35e9bd..16d850dc 100644
--- a/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
+++ b/third_party/freetype/include/freetype-custom/freetype/config/ftoption.h
@@ -435,6 +435,23 @@
 
   /**************************************************************************
    *
+   * Logging
+   *
+   *   Compiling FreeType in debug or trace mode makes FreeType write error
+   *   and trace log messages to `stderr`.  Enabling this macro
+   *   automatically forces the `FT_DEBUG_LEVEL_ERROR` and
+   *   `FT_DEBUG_LEVEL_TRACE` macros and allows FreeType to write error and
+   *   trace log messages to a file instead of `stderr`.  For writing logs
+   *   to a file, FreeType uses an the external `dlg` library (the source
+   *   code is in `src/dlg`).
+   *
+   *   This option needs a C99 compiler.
+   */
+/* #define FT_LOGGING */
+
+
+  /**************************************************************************
+   *
    * Autofitter debugging
    *
    *   If `FT_DEBUG_AUTOFIT` is defined, FreeType provides some means to
diff --git a/tools/cfi/ignores.txt b/tools/cfi/ignores.txt
index 4dc4b3b..14ece19 100644
--- a/tools/cfi/ignores.txt
+++ b/tools/cfi/ignores.txt
@@ -169,6 +169,7 @@
 
 # Calls to vulkan function pointers from shared library.
 src:*third_party/vulkan_memory_allocator/src/vk_mem_alloc.h
+src:*third_party/angle/third_party/vulkan-loader/src/loader*
 
 src:*components/os_crypt/*
 
diff --git a/tools/grit/PRESUBMIT.py b/tools/grit/PRESUBMIT.py
index 43d3e98f..d82b5b3 100644
--- a/tools/grit/PRESUBMIT.py
+++ b/tools/grit/PRESUBMIT.py
@@ -16,8 +16,6 @@
       output_api, [
           input_api.os_path.join('grit', 'test_suite_all.py'),
           input_api.os_path.join(input_api.PresubmitLocalPath(),
-                                 'preprocess_grit_test.py'),
-          input_api.os_path.join(input_api.PresubmitLocalPath(),
                                  'preprocess_if_expr_test.py')
       ],
       run_on_python3=False)  # See https://crbug.com/1145395.
diff --git a/tools/grit/preprocess_grit.gni b/tools/grit/preprocess_grit.gni
deleted file mode 100644
index 6607147..0000000
--- a/tools/grit/preprocess_grit.gni
+++ /dev/null
@@ -1,53 +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.
-
-# This tool is no longer supported. Use preprocess_if_expr instead, which
-# also generates manifests and preprocesses <if expr> but does not also
-# inline <include>s (which are deprecated).
-
-import("//build/config/python.gni")
-import("//tools/grit/grit_defines.gni")
-
-template("preprocess_grit") {
-  # TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
-  python2_action(target_name) {
-    script = "//tools/grit/preprocess_grit.py"
-
-    if (defined(invoker.deps)) {
-      deps = invoker.deps
-    }
-
-    inputs = []
-    outputs = []
-    foreach(in_file, invoker.in_files) {
-      inputs += [ invoker.in_folder + "/" + in_file ]
-      outputs += [ invoker.out_folder + "/" + in_file ]
-    }
-
-    args = [
-             "--in-folder",
-             rebase_path(invoker.in_folder, root_build_dir),
-             "--out-folder",
-             rebase_path(invoker.out_folder, root_build_dir),
-             "--in-files",
-           ] + invoker.in_files + grit_defines
-
-    if (defined(invoker.defines)) {
-      foreach(define, invoker.defines) {
-        args += [
-          "-D",
-          define,
-        ]
-      }
-    }
-
-    if (defined(invoker.out_manifest)) {
-      args += [
-        "--out-manifest",
-        rebase_path(invoker.out_manifest, root_build_dir),
-      ]
-      outputs += [ invoker.out_manifest ]
-    }
-  }
-}
diff --git a/tools/grit/preprocess_grit.py b/tools/grit/preprocess_grit.py
deleted file mode 100644
index 6294be1a..0000000
--- a/tools/grit/preprocess_grit.py
+++ /dev/null
@@ -1,101 +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.
-
-import argparse
-import errno
-import io
-import json
-import os
-import sys
-
-import grit.format.html_inline
-import grit.node.base
-import grit.util
-
-_CWD = os.getcwd()
-
-
-class PreprocessNode(grit.node.base.Node):
-  def __init__(self):
-    super(PreprocessNode, self).__init__()
-
-  def ProcessFile(self, filepath):
-    return grit.format.html_inline.InlineToString(
-        filepath,
-        self,
-        preprocess_only=True,
-        allow_external_script=False,
-        strip_whitespace=False,
-        rewrite_function=None,
-        filename_expansion_function=None)
-
-  def EvaluateCondition(self, expr):
-    return grit.node.base.Node.EvaluateExpression(expr, self.defines,
-                                                  self.target_platform, {})
-
-  def SetDefines(self, defines):
-    self.defines = defines
-
-  def SetTargetPlatform(self, target_platform):
-    self.target_platform = target_platform
-
-  @staticmethod
-  def Construct(defines, target_platform):
-    node = PreprocessNode()
-    node.SetDefines(defines)
-    node.SetTargetPlatform(target_platform or sys.platform)
-    return node
-
-
-def main(argv):
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--in-folder', required=True)
-  parser.add_argument('--out-folder', required=True)
-  parser.add_argument('--out-manifest')
-  parser.add_argument('--in-files', required=True, nargs="*")
-  parser.add_argument('-D', '--defines', nargs="*", action='append')
-  parser.add_argument('-E', '--environment')
-  parser.add_argument('-t', '--target')
-  args = parser.parse_args(argv)
-
-  in_folder = os.path.normpath(os.path.join(_CWD, args.in_folder))
-  out_folder = os.path.normpath(os.path.join(_CWD, args.out_folder))
-
-  defines = {}
-  for define_arg in args.defines:
-    define, = define_arg
-    name, val = grit.util.ParseDefine(define)
-    defines[name] = val
-
-  node = PreprocessNode.Construct(defines, args.target)
-
-  for input_file in args.in_files:
-    output = node.ProcessFile(os.path.join(in_folder, input_file))
-
-    out_path = os.path.join(out_folder, input_file)
-    out_dir = os.path.dirname(out_path)
-    assert out_dir.startswith(out_folder), \
-           'Cannot preprocess files to locations not under %s.' % out_dir
-    try:
-      os.makedirs(out_dir)
-    except OSError as e:
-      # Ignore directory exists errors. This can happen if two build rules
-      # for overlapping directories hit the makedirs line at the same time.
-      if e.errno != errno.EEXIST:
-        raise
-    with io.open(out_path, mode='wb') as f:
-      f.write(output.encode('utf-8'))
-
-  if args.out_manifest:
-    manifest_data = {}
-    manifest_data['base_dir'] = '%s' % args.out_folder
-    manifest_data['files'] = args.in_files
-    manifest_file = open(
-        os.path.normpath(os.path.join(_CWD, args.out_manifest)), 'wb')
-    json.dump(manifest_data, manifest_file)
-  return
-
-
-if __name__ == '__main__':
-  main(sys.argv[1:])
diff --git a/tools/grit/preprocess_grit_test.py b/tools/grit/preprocess_grit_test.py
deleted file mode 100644
index d0743d4..0000000
--- a/tools/grit/preprocess_grit_test.py
+++ /dev/null
@@ -1,49 +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.
-
-import os
-import shutil
-import tempfile
-import unittest
-
-import preprocess_grit
-
-_HERE_DIR = os.path.dirname(__file__)
-
-
-class PreprocessGritTest(unittest.TestCase):
-  def setUp(self):
-    self._out_folder = None
-
-  def tearDown(self):
-    if self._out_folder:
-      shutil.rmtree(self._out_folder)
-
-  def _read_out_file(self, file_name):
-    assert self._out_folder
-    return open(os.path.join(self._out_folder, file_name), 'r').read()
-
-  def _run_test(self, defines, file_name):
-    assert not self._out_folder
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
-    preprocess_grit.main([
-        '--in-folder',
-        os.path.join(_HERE_DIR, 'preprocess_tests'),
-        '--out-folder',
-        self._out_folder,
-        '--in-files',
-        file_name,
-    ] + defines)
-
-  def testPreprocess(self):
-    self._run_test(['-D', 'foo', '-D', 'bar'], 'test_with_ifexpr.js')
-    actual = self._read_out_file('test_with_ifexpr.js')
-    self.assertIn('I should be included in HTML', actual)
-    self.assertIn('I should be included in JS', actual)
-    self.assertNotIn('I should be excluded from HTML', actual)
-    self.assertNotIn('I should be excluded from JS', actual)
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/tools/infra/clobber_cache_utils.py b/tools/infra/clobber_cache_utils.py
index ba180373..8fef31d 100644
--- a/tools/infra/clobber_cache_utils.py
+++ b/tools/infra/clobber_cache_utils.py
@@ -6,56 +6,47 @@
 
 from __future__ import print_function
 
+import json
 import os
 import subprocess
-import sys
 import textwrap
 
 _SRC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
-_SWARMING_CLIENT = os.path.join(_SRC_ROOT, 'tools', 'swarming_client',
-                                'swarming.py')
+_SWARMING_CLIENT = os.path.join(_SRC_ROOT, 'tools', 'luci-go', 'swarming')
 _SWARMING_SERVER = 'chromium-swarm.appspot.com'
 
 
 def _get_bots(swarming_server, pool, cache):
   cmd = [
-      sys.executable,
       _SWARMING_CLIENT,
       'bots',
-      '-b',
       '-S',
       swarming_server,
-      '-d',
-      'caches',
-      cache,
-      '-d',
-      'pool',
-      pool,
+      '-dimension',
+      'caches=' + cache,
+      '-dimension',
+      'pool=' + pool,
   ]
-  return subprocess.check_output(cmd).splitlines()
+  return [bot['bot_id'] for bot in json.loads(subprocess.check_output(cmd))]
 
 
 def _trigger_clobber(swarming_server, pool, cache, bot, mount_rel_path,
                      dry_run):
   cmd = [
-      sys.executable,
       _SWARMING_CLIENT,
       'trigger',
       '-S',
       swarming_server,
-      '-d',
-      'pool',
-      pool,
-      '-d',
-      'id',
-      bot,
-      '--cipd-package',
-      'cpython:infra/python/cpython/${platform}:latest',
-      '--named-cache',
-      cache,
-      mount_rel_path,
-      '--priority=10',
-      '--raw-cmd',
+      '-dimension',
+      'pool=' + pool,
+      '-dimension',
+      'id=' + bot,
+      '-cipd-package',
+      'cpython:infra/python/cpython/${platform}=latest',
+      '-named-cache',
+      cache + '=' + mount_rel_path,
+      '-priority',
+      '10',
       '--',
       'cpython/bin/python${EXECUTABLE_SUFFIX}',
       '-c',
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e0520e9..aa3a42d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -28107,7 +28107,7 @@
   <int value="1431" label="PointerEventSetCapture"/>
   <int value="1432" label="PointerEventDispatch"/>
   <int value="1433" label="MIDIMessageEventReceivedTime"/>
-  <int value="1434" label="SummaryElementWithDisplayBlockAuthorRule"/>
+  <int value="1434" label="OBSOLETE_SummaryElementWithDisplayBlockAuthorRule"/>
   <int value="1435" label="V8MediaStream_Active_AttributeGetter"/>
   <int value="1436" label="BeforeInstallPromptEvent"/>
   <int value="1437" label="BeforeInstallPromptEventUserChoice"/>
@@ -30110,7 +30110,7 @@
   <int value="3334" label="ContentVisibilityHiddenMatchable"/>
   <int value="3335" label="InlineOverflowAutoWithInlineEndPadding"/>
   <int value="3336" label="InlineOverflowScrollWithInlineEndPadding"/>
-  <int value="3337" label="CSSSelectorPseudoWebKitDetailsMarker"/>
+  <int value="3337" label="OBSOLETE_CSSSelectorPseudoWebKitDetailsMarker"/>
   <int value="3338" label="SerialPortGetInfo"/>
   <int value="3339" label="FileSystemPickerMethod"/>
   <int value="3340" label="V8Window_ShowOpenFilePicker_Method"/>
@@ -30522,6 +30522,8 @@
   <int value="3740" label="CrossOriginStrictNosniffWouldBlock"/>
   <int value="3741" label="CSSSelectorPseudoDir"/>
   <int value="3742" label="CrossOriginSubframeWithoutEmbeddingControl"/>
+  <int value="3743" label="ReadableStreamWithByteSource"/>
+  <int value="3744" label="ReadableStreamBYOBReader"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -42533,6 +42535,7 @@
   <int value="-1461261930" label="OutOfBlinkCORS:disabled"/>
   <int value="-1460598402"
       label="EnterpriseReportingApiKeychainRecreation:disabled"/>
+  <int value="-1460561942" label="SystemEmojiPicker:disabled"/>
   <int value="-1460462432" label="disable-media-source"/>
   <int value="-1457775295" label="PasswordSearchMobile:disabled"/>
   <int value="-1456789591" label="MediaFoundationVideoCapture:enabled"/>
@@ -44199,6 +44202,7 @@
   <int value="120519004" label="NavigationPredictor:disabled"/>
   <int value="121684313" label="QuickUnlockPin:enabled"/>
   <int value="121858954" label="enable-supervised-user-safesites"/>
+  <int value="121911155" label="DesktopPWAsElidedExtensionsMenu:enabled"/>
   <int value="123097915" label="FaviconsFromWebManifest:enabled"/>
   <int value="125581289" label="WebRtcHWVP8Encoding:disabled"/>
   <int value="125934378" label="enable-password-link"/>
@@ -44570,6 +44574,7 @@
   <int value="500947367"
       label="WebAuthenticationCrosPlatformAuthenticator:disabled"/>
   <int value="501477022" label="DrawOcclusion:enabled"/>
+  <int value="501899677" label="SystemEmojiPicker:enabled"/>
   <int value="502551931" label="IncognitoStrings:enabled"/>
   <int value="503189154" label="ViewsBrowserWindows:disabled"/>
   <int value="503245473" label="disable-translate-new-ux"/>
@@ -45497,6 +45502,7 @@
       label="OmniboxUIExperimentHideSuggestionUrlTrivialSubdomains:disabled"/>
   <int value="1387356699" label="PolicyAtomicGroup:enabled"/>
   <int value="1389729816" label="data-reduction-proxy-lo-fi"/>
+  <int value="1392836587" label="DesktopPWAsElidedExtensionsMenu:disabled"/>
   <int value="1392935139" label="KidsManagementUrlClassification:enabled"/>
   <int value="1393413889" label="SeparatePointingStickSettings:disabled"/>
   <int value="1393500952" label="EnableVirtualKeyboardUkm:disabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/network/histograms.xml b/tools/metrics/histograms/histograms_xml/network/histograms.xml
index c29e331..e198f64 100644
--- a/tools/metrics/histograms/histograms_xml/network/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/network/histograms.xml
@@ -1325,6 +1325,38 @@
   </summary>
 </histogram>
 
+<histogram name="Network.Shill.Wifi.LinkMonitorsDetectionTimeDiff.ArpBetter"
+    units="ms" expires_after="M91">
+  <owner>jiejiang@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
+  <summary>
+    Chrome OS metric that tracks the number of milliseconds between the
+    detection times for the same failure of NeighborLinkMonitor and
+    shill::LinkMonitor. Recorded after either of the link monitor detects an
+    error. The larger this value is, the better shill::LinkMonitor performs than
+    NeighborLinkMonitor. 0 means shill::LinkMonitor is no better than
+    NeighborLinkMonitor. If shill::LinkMonitor detects a failure but
+    NeighborLinkMonitor does not, the maximum value of a timeout will be
+    emitted.
+  </summary>
+</histogram>
+
+<histogram
+    name="Network.Shill.Wifi.LinkMonitorsDetectionTimeDiff.NeighborBetter"
+    units="ms" expires_after="M91">
+  <owner>jiejiang@chromium.org</owner>
+  <owner>cros-network-metrics@google.com</owner>
+  <summary>
+    Chrome OS metric that tracks the number of milliseconds between the
+    detection times for the same failure of patchpanel::NeighborLinkMonitor and
+    shill::LinkMonitor. Recorded after either of the link monitor detects an
+    error. The larger this value is, the better NeighborLinkMonitor performs
+    than shill::LinkMonitor. 0 means NeighborLinkMonitor is no better than
+    shill::LinkMonitor. If NeighborLinkMonitor detects a failure but
+    shill::LinkMonitor does not, the maxmium value of a timeout will be emitted.
+  </summary>
+</histogram>
+
 <histogram name="Network.Shill.Wifi.LinkMonitorSecondsToFailure"
     units="seconds" expires_after="2021-12-01">
   <owner>briannorris@chromium.org</owner>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index a21304c8..13ea4bf 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -71,4 +71,4 @@
 v8.browsing_mobile-future,"mythria@chromium.org, tmrts@chromium.org",Blink>JavaScript,,"2018,2019,2020,emerging_market,health_check,images,infinite_scroll,international,javascript_heavy"
 v8.runtime_stats.top_25,"mythria@chromium.org, ulan@chromium.org",Blink>JavaScript,,"cold,hot,warm"
 views_perftests,tapted@chromium.org,Internals>Views,,
-webrtc,"qiangchen@chromium.org, mbonadei@chromium.org",Blink>WebRTC,http://bit.ly/webrtc-benchmark,"datachannel,getusermedia,pauseplay,smoothness,stress,videoConstraints"
+webrtc,"qiangchen@chromium.org, mbonadei@chromium.org",Blink>WebRTC,http://bit.ly/webrtc-benchmark,"datachannel,getusermedia,insertableStreams,pauseplay,smoothness,stress,videoConstraints"
diff --git a/tools/perf/page_sets/press_story.py b/tools/perf/page_sets/press_story.py
index 57b1369..7209d72a 100644
--- a/tools/perf/page_sets/press_story.py
+++ b/tools/perf/page_sets/press_story.py
@@ -29,13 +29,15 @@
   DETERMINISTIC_JS = False
   NAME = None
 
-  def __init__(self, ps, tags=None):
-    super(PressStory, self).__init__(
-        self.URL, ps,
-        base_dir=ps.base_dir,
-        make_javascript_deterministic=self.DETERMINISTIC_JS,
-        name=self.NAME if self.NAME else self.URL,
-        tags=tags)
+  def __init__(self, ps, tags=None, extra_browser_args=None):
+    super(PressStory,
+          self).__init__(self.URL,
+                         ps,
+                         base_dir=ps.base_dir,
+                         make_javascript_deterministic=self.DETERMINISTIC_JS,
+                         name=self.NAME if self.NAME else self.URL,
+                         tags=tags,
+                         extra_browser_args=extra_browser_args)
     self._measurements = []
     self._action_runner = None
 
diff --git a/tools/perf/page_sets/update_webrtc_cases b/tools/perf/page_sets/update_webrtc_cases
index 2024f2b0f..d528cd8 100755
--- a/tools/perf/page_sets/update_webrtc_cases
+++ b/tools/perf/page_sets/update_webrtc_cases
@@ -31,8 +31,9 @@
         'dirs': [
             'src/content/datachannel/datatransfer',
             'src/content/getusermedia/resolution',
+            'src/content/insertable-streams/video-processing',
         ],
-        'revision': 'a4ca7fbc92825b4993d9a7fa1844206e0c718454',
+        'revision': 'bcccf04dcbbb1c1061c76712b0f501e117d8a1bd',
     },
 }
 
@@ -68,16 +69,23 @@
       self._closed = True
 
 
-def CopyJSFile(origin, destination):
-  contents = []
-  with open(origin) as input_file:
-    contents = input_file.readlines()
+def ConcatJSDir(origin, destination):
+  dest_contents = [JS_COPYRIGHT_NOTICE]
 
-  contents = contents[COPYRIGHT_NOTICE_LENGTH:]
-  contents = [JS_COPYRIGHT_NOTICE] + contents
+  for filename in sorted(os.listdir(origin)):
+    if filename.endswith('.js'):
+      with open(os.path.join(origin, filename)) as input_file:
+       src_contents = input_file.readlines()
+       dest_contents += src_contents[COPYRIGHT_NOTICE_LENGTH:]
+
+
+  # Replace references to chrome.webm with road_trip_640_480.mp4, which has
+  # already been uploaded to cloud storage.
+  dest_contents = [re.sub('(../)+video/chrome.webm', 'road_trip_640_480.mp4', x)
+                   for x in dest_contents]
 
   with open(destination, 'w') as output_file:
-    output_file.writelines(contents)
+    output_file.writelines(dest_contents)
 
 
 def CopyHTMLFile(test_name, origin, destination):
@@ -108,10 +116,11 @@
           '  * Adds a copyright notice on top of the HTML and JS files.\n'
           '  * Deletes the <meta> tags.\n'
           '  * Discards the CSS files and corresponding link tags.\n'
-          '  * Discards the JS files and corresponding script tags except for '
-          'main.js.\n'
-          '  * Renames the index.html and main.js files for each test to '
-          'testname.html and testname.js.'))
+          '  * Renames the index.html to testname.html.\n'
+          '  * Concatenates the js files to testname.js, removing relative '
+          'paths to video resources to allow WPR recording.\n'
+          '  * Removes all script tags and adds a single script tag for '
+          'testname.js.'))
 
   parser.add_argument('-d', '--destination', default=DEFAULT_DESTINATION_DIR,
                       type=str, help='Where to save the WebRTC test pages.')
@@ -136,8 +145,8 @@
         test_dir = os.path.join(repo_dir, test_dir)
         test_name = os.path.basename(test_dir)
 
-        CopyJSFile(os.path.join(test_dir, 'js', 'main.js'),
-                   os.path.join(args.destination, test_name + '.js'))
+        ConcatJSDir(os.path.join(test_dir, 'js'),
+                    os.path.join(args.destination, test_name + '.js'))
         CopyHTMLFile(test_name, os.path.join(test_dir, 'index.html'),
                      os.path.join(args.destination, test_name + '.html'))
 
diff --git a/tools/perf/page_sets/webrtc_cases.py b/tools/perf/page_sets/webrtc_cases.py
index 5dc1cbb..663fd78 100644
--- a/tools/perf/page_sets/webrtc_cases.py
+++ b/tools/perf/page_sets/webrtc_cases.py
@@ -8,11 +8,13 @@
 
 class WebrtcPage(press_story.PressStory):
 
-  def __init__(self, url, page_set, name, tags):
+  def __init__(self, url, page_set, name, tags, extra_browser_args=None):
     assert url.startswith('file://webrtc_cases/')
     self.URL = url
     self.NAME = name
-    super(WebrtcPage, self).__init__(page_set, tags=tags)
+    super(WebrtcPage, self).__init__(page_set,
+                                     tags=tags,
+                                     extra_browser_args=extra_browser_args)
 
 
 class GetUserMedia(WebrtcPage):
@@ -126,6 +128,61 @@
     action_runner.Wait(20)
 
 
+class InsertableStreamsVideoProcessing(WebrtcPage):
+  """Why: processes/transforms video in various ways."""
+
+  def __init__(self, page_set, source, transform, sink, tags):
+    super(InsertableStreamsVideoProcessing, self).__init__(
+        url='file://webrtc_cases/video-processing.html',
+        name=('insertable_streams_video_processing_%s_%s_%s' %
+              (source, transform, sink)),
+        page_set=page_set,
+        tags=tags,
+        extra_browser_args=(
+            '--enable-blink-features=WebCodecs,MediaStreamInsertableStreams'))
+    self.source = source
+    self.transform = transform
+    self.sink = sink
+    self.supported = None
+
+  def RunNavigateSteps(self, action_runner):
+    self.supported = action_runner.EvaluateJavaScript(
+        "typeof MediaStreamTrackProcessor !== 'undefined' &&"
+        "typeof MediaStreamTrackGenerator !== 'undefined'")
+    if self.supported:
+      super(InsertableStreamsVideoProcessing,
+            self).RunNavigateSteps(action_runner)
+
+  def ExecuteTest(self, action_runner):
+    self.AddMeasurement(
+        'supported', 'count', 1 if self.supported else 0,
+        'Boolean flag indicating if this benchmark is supported by the browser.'
+    )
+    if not self.supported:
+      return
+    with action_runner.CreateInteraction('Start_Pipeline', repeatable=True):
+      action_runner.WaitForElement('select[id="sourceSelector"]:enabled')
+      action_runner.ExecuteJavaScript(
+          'document.getElementById("sourceSelector").value="%s";' % self.source)
+      action_runner.WaitForElement('select[id="transformSelector"]:enabled')
+      action_runner.ExecuteJavaScript(
+          'document.getElementById("transformSelector").value="%s";' %
+          self.transform)
+      action_runner.WaitForElement('select[id="sinkSelector"]:enabled')
+      action_runner.ExecuteJavaScript(
+          'document.getElementById("sinkSelector").value="%s";' % self.sink)
+      action_runner.ExecuteJavaScript(
+          'document.getElementById("sourceSelector").dispatchEvent('
+          '  new InputEvent("input", {}));')
+      action_runner.WaitForElement('.sinkVideo')
+      action_runner.Wait(10)
+    self.AddJavaScriptMeasurement(
+        'sink_decoded_frames',
+        'count',
+        'document.querySelector(".sinkVideo").webkitDecodedFrameCount',
+        description='Number of frames received at the sink video.')
+
+
 class WebrtcPageSet(story.StorySet):
   def __init__(self):
     super(WebrtcPageSet, self).__init__(
@@ -139,3 +196,39 @@
     self.AddStory(VideoCodecConstraints(self, 'H264', tags=['videoConstraints']))
     self.AddStory(VideoCodecConstraints(self, 'VP8', tags=['videoConstraints']))
     self.AddStory(VideoCodecConstraints(self, 'VP9', tags=['videoConstraints']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'camera',
+                                         'webgl',
+                                         'video',
+                                         tags=['insertableStreams']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'video',
+                                         'webgl',
+                                         'video',
+                                         tags=['insertableStreams']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'pc',
+                                         'webgl',
+                                         'video',
+                                         tags=['insertableStreams']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'camera',
+                                         'canvas2d',
+                                         'video',
+                                         tags=['insertableStreams']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'camera',
+                                         'drop',
+                                         'video',
+                                         tags=['insertableStreams']))
+    self.AddStory(
+        InsertableStreamsVideoProcessing(self,
+                                         'camera',
+                                         'webgl',
+                                         'pc',
+                                         tags=['insertableStreams']))
diff --git a/tools/perf/page_sets/webrtc_cases/road_trip_640_480.mp4.sha1 b/tools/perf/page_sets/webrtc_cases/road_trip_640_480.mp4.sha1
new file mode 100644
index 0000000..a5ecc247
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/road_trip_640_480.mp4.sha1
@@ -0,0 +1 @@
+0a225539a4d8c0ed4c2a8a01f12f4611e4fa6566
\ No newline at end of file
diff --git a/tools/perf/page_sets/webrtc_cases/video-processing.html b/tools/perf/page_sets/webrtc_cases/video-processing.html
new file mode 100644
index 0000000..5cc72f37
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/video-processing.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<!--
+ * 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.
+-->
+<html>
+<head>
+
+
+    <base target="_blank">
+
+    <title>Insertable Streams - Video</title>
+
+
+</head>
+
+<body>
+
+<div id="container">
+    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a>
+        <span>Video processing with insertable streams</span></h1>
+
+    <p>This sample shows how to perform processing on a video stream using the experimental
+        <a href="https://github.com/w3c/mediacapture-insertable-streams">insertable streams</a> API.
+        There are options for the source of the input stream, the destination of the output stream,
+        and the API used to transform the stream. There is also the option to duplicate the source
+        stream to a video element on the page, which may affect the source FPS.
+    </p>
+
+    <span id="outputVideo"></span>
+
+    <div class="box">
+        <span>Source:</span>
+        <select id="sourceSelector" disabled>
+            <option selected value="">(stopped)</option>
+            <option value="camera">Camera</option>
+            <option value="video">Video</option>
+            <option value="pc">Peer connection (from camera)</option>
+        </select>
+        <span>Add to page: <input type="checkbox" id="sourceVisible" disabled></span>
+    </div>
+    <div class="box">
+        <span>Transform:</span>
+        <select id="transformSelector" disabled>
+            <option selected value="webgl">WebGL</option>
+            <option value="canvas2d">Canvas2D</option>
+            <option value="drop">Drop frames at random</option>
+            <option value="delay">Delay all frames by 100ms</option>
+        </select>
+    </div>
+    <div class="box">
+        <span>Destination:</span>
+        <select id="sinkSelector" disabled>
+            <option selected value="video">Video</option>
+            <option value="pc">Peer connection</option>
+        </select>
+    </div>
+
+    <p>View the console to see logging.</p>
+
+    <p>
+        <b>Note</b>: This sample is using an experimental API that has not yet been standardized. As
+        of 2020-11-16, this API is available in Chrome M88 if the experimental code is enabled on
+        the command line with
+        <code>--enable-blink-features=WebCodecs,MediaStreamInsertableStreams</code>.
+    </p>
+    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/insertable-streams/video-processing"
+       title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
+
+</div>
+
+
+<script src="video-processing.js"></script>
+</body></html>
diff --git a/tools/perf/page_sets/webrtc_cases/video-processing.js b/tools/perf/page_sets/webrtc_cases/video-processing.js
new file mode 100644
index 0000000..76dd056
--- /dev/null
+++ b/tools/perf/page_sets/webrtc_cases/video-processing.js
@@ -0,0 +1,1269 @@
+/*
+ * 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.
+ */
+'use strict';
+
+/* global VideoMirrorHelper */ // defined in video-mirror-helper.js
+
+/**
+ * Opens the device's camera with getUserMedia.
+ * @implements {MediaStreamSource} in pipeline.js
+ */
+class CameraSource { // eslint-disable-line no-unused-vars
+  constructor() {
+    /**
+     * @private @const {!VideoMirrorHelper} manages displaying the video stream
+     *     in the page
+     */
+    this.videoMirrorHelper_ = new VideoMirrorHelper();
+    /** @private {?MediaStream} camera stream, initialized in getMediaStream */
+    this.stream_ = null;
+    /** @private {string} */
+    this.debugPath_ = '<unknown>';
+  }
+  /** @override */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+    this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);
+  }
+  /** @override */
+  setVisibility(visible) {
+    this.videoMirrorHelper_.setVisibility(visible);
+  }
+  /** @override */
+  async getMediaStream() {
+    if (this.stream_) return this.stream_;
+    console.log('[CameraSource] Requesting camera.');
+    this.stream_ =
+        await navigator.mediaDevices.getUserMedia({audio: false, video: true});
+    console.log(
+        '[CameraSource] Received camera stream.',
+        `${this.debugPath_}.stream_ =`, this.stream_);
+    this.videoMirrorHelper_.setStream(this.stream_);
+    return this.stream_;
+  }
+  /** @override */
+  destroy() {
+    console.log('[CameraSource] Stopping camera');
+    this.videoMirrorHelper_.destroy();
+    if (this.stream_) {
+      this.stream_.getTracks().forEach(t => t.stop());
+    }
+  }
+}
+'use strict';
+
+/**
+ * Applies a picture-frame effect using CanvasRenderingContext2D.
+ * @implements {FrameTransform} in pipeline.js
+ */
+class CanvasTransform { // eslint-disable-line no-unused-vars
+  constructor() {
+    // All fields are initialized in init()
+    /** @private {?OffscreenCanvas} canvas used to create the 2D context */
+    this.canvas_ = null;
+    /**
+     * @private {?CanvasRenderingContext2D} the 2D context used to draw the
+     *     effect
+     */
+    this.ctx_ = null;
+    /** @private {string} */
+    this.debugPath_ = 'debug.pipeline.frameTransform_';
+  }
+  /** @override */
+  async init() {
+    console.log('[CanvasTransform] Initializing 2D context for transform');
+    this.canvas_ = new OffscreenCanvas(1, 1);
+    this.ctx_ = /** @type {?CanvasRenderingContext2D} */ (
+      this.canvas_.getContext('2d', {alpha: false, desynchronized: true}));
+    if (!this.ctx_) {
+      throw new Error('Unable to create CanvasRenderingContext2D');
+    }
+    console.log(
+        '[CanvasTransform] CanvasRenderingContext2D initialized.',
+        `${this.debugPath_}.canvas_ =`, this.canvas_,
+        `${this.debugPath_}.ctx_ =`, this.ctx_);
+  }
+
+  /** @override */
+  async transform(frame, controller) {
+    const ctx = this.ctx_;
+    if (!this.canvas_ || !ctx) {
+      frame.destroy();
+      return;
+    }
+    const width = frame.displayWidth;
+    const height = frame.displayHeight;
+    this.canvas_.width = width;
+    this.canvas_.height = height;
+    // VideoFrame.timestamp is technically optional, but that should never
+    // happen here.
+    // TODO(benjaminwagner): Follow up if we should change the spec so this is
+    // non-optional.
+    const timestamp = /** @type {number} */ (frame.timestamp);
+    const inputBitmap = await frame.createImageBitmap();
+    frame.destroy();
+
+    ctx.drawImage(inputBitmap, 0, 0);
+    inputBitmap.close();
+
+    ctx.shadowColor = '#000';
+    ctx.shadowBlur = 20;
+    ctx.lineWidth = 50;
+    ctx.strokeStyle = '#000';
+    ctx.strokeRect(0, 0, width, height);
+
+    const outputBitmap = await createImageBitmap(this.canvas_);
+    const outputFrame = new VideoFrame(outputBitmap, {timestamp});
+    outputBitmap.close();
+    controller.enqueue(outputFrame);
+  }
+
+  /** @override */
+  destroy() {}
+}
+'use strict';
+
+/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */
+if (typeof MediaStreamTrackProcessor === 'undefined' ||
+    typeof MediaStreamTrackGenerator === 'undefined') {
+  alert(
+      'Your browser does not support the experimental MediaStreamTrack API ' +
+      'for Insertable Streams of Media. See the note at the bottom of the ' +
+      'page.');
+}
+
+/* global CameraSource */ // defined in camera-source.js
+/* global CanvasTransform */ // defined in canvas-transform.js
+/* global PeerConnectionSink */ // defined in peer-connection-sink.js
+/* global PeerConnectionSource */ // defined in peer-connection-source.js
+/* global Pipeline */ // defined in pipeline.js
+/* global DropTransform, DelayTransform */ // defined in simple-transforms.js
+/* global VideoSink */ // defined in video-sink.js
+/* global VideoSource */ // defined in video-source.js
+/* global WebGLTransform */ // defined in webgl-transform.js
+
+/**
+ * Allows inspecting objects in the console. See console log messages for
+ * attributes added to this debug object.
+ * @type {!Object<string,*>}
+ */
+let debug = {};
+
+/**
+ * FrameTransformFn applies a transform to a frame and queues the output frame
+ * (if any) using the controller. The first argument is the input frame and the
+ * second argument is the stream controller.
+ * The VideoFrame should be destroyed as soon as it is no longer needed to free
+ * resources and maintain good performance.
+ * @typedef {function(
+ *     !VideoFrame,
+ *     !TransformStreamDefaultController<!VideoFrame>): !Promise<undefined>}
+ */
+let FrameTransformFn; // eslint-disable-line no-unused-vars
+
+/**
+ * Creates a pair of MediaStreamTrackProcessor and MediaStreamTrackGenerator
+ * that applies transform to sourceTrack. This function is the core part of the
+ * sample, demonstrating how to use the new API.
+ * @param {!MediaStreamTrack} sourceTrack the video track to be transformed. The
+ *     track can be from any source, e.g. getUserMedia, RTCTrackEvent, or
+ *     captureStream on HTMLMediaElement or HTMLCanvasElement.
+ * @param {!FrameTransformFn} transform the transform to apply to sourceTrack;
+ *     the transformed frames are available on the returned track. See the
+ *     implementations of FrameTransform.transform later in this file for
+ *     examples.
+ * @param {!AbortSignal} signal can be used to stop processing
+ * @return {!MediaStreamTrack} the result of sourceTrack transformed using
+ *     transform.
+ */
+// eslint-disable-next-line no-unused-vars
+function createProcessedMediaStreamTrack(sourceTrack, transform, signal) {
+  // Create the MediaStreamTrackProcessor.
+  /** @type {?MediaStreamTrackProcessor<!VideoFrame>} */
+  let processor;
+  try {
+    processor = new MediaStreamTrackProcessor(sourceTrack);
+  } catch (e) {
+    alert(`MediaStreamTrackProcessor failed: ${e}`);
+    throw e;
+  }
+
+  // Create the MediaStreamTrackGenerator.
+  /** @type {?MediaStreamTrackGenerator<!VideoFrame>} */
+  let generator;
+  try {
+    generator = new MediaStreamTrackGenerator('video');
+  } catch (e) {
+    alert(`MediaStreamTrackGenerator failed: ${e}`);
+    throw e;
+  }
+
+  const source = processor.readable;
+  const sink = generator.writable;
+
+  // Create a TransformStream using our FrameTransformFn. (Note that the
+  // "Stream" in TransformStream refers to the Streams API, specified by
+  // https://streams.spec.whatwg.org/, not the Media Capture and Streams API,
+  // specified by https://w3c.github.io/mediacapture-main/.)
+  /** @type {!TransformStream<!VideoFrame, !VideoFrame>} */
+  const transformer = new TransformStream({transform});
+
+  // Apply the transform to the processor's stream and send it to the
+  // generator's stream.
+  const promise = source.pipeThrough(transformer, {signal}).pipeTo(sink);
+
+  promise.catch((e) => {
+    if (signal.aborted) {
+      console.log(
+          '[createProcessedMediaStreamTrack] Shutting down streams after abort.');
+    } else {
+      console.error(
+          '[createProcessedMediaStreamTrack] Error from stream transform:', e);
+    }
+    source.cancel(e);
+    sink.abort(e);
+  });
+
+  debug['processor'] = processor;
+  debug['generator'] = generator;
+  debug['transformStream'] = transformer;
+  console.log(
+      '[createProcessedMediaStreamTrack] Created MediaStreamTrackProcessor, ' +
+          'MediaStreamTrackGenerator, and TransformStream.',
+      'debug.processor =', processor, 'debug.generator =', generator,
+      'debug.transformStream =', transformer);
+
+  return generator;
+}
+
+/**
+ * The current video pipeline. Initialized by initPipeline().
+ * @type {?Pipeline}
+ */
+let pipeline;
+
+/**
+ * Sets up handlers for interacting with the UI elements on the page.
+ */
+function initUI() {
+  const sourceSelector = /** @type {!HTMLSelectElement} */ (
+    document.getElementById('sourceSelector'));
+  const sourceVisibleCheckbox = (/** @type {!HTMLInputElement} */ (
+    document.getElementById('sourceVisible')));
+  /**
+   * Updates the pipeline based on the current settings of the sourceSelector
+   * and sourceVisible UI elements. Unlike updatePipelineSource(), never
+   * re-initializes the pipeline.
+   */
+  function updatePipelineSourceIfSet() {
+    const sourceType =
+        sourceSelector.options[sourceSelector.selectedIndex].value;
+    if (!sourceType) return;
+    console.log(`[UI] Selected source: ${sourceType}`);
+    let source;
+    switch (sourceType) {
+      case 'camera':
+        source = new CameraSource();
+        break;
+      case 'video':
+        source = new VideoSource();
+        break;
+      case 'pc':
+        source = new PeerConnectionSource(new CameraSource());
+        break;
+      default:
+        alert(`unknown source ${sourceType}`);
+        return;
+    }
+    source.setVisibility(sourceVisibleCheckbox.checked);
+    pipeline.updateSource(source);
+  }
+  /**
+   * Updates the pipeline based on the current settings of the sourceSelector
+   * and sourceVisible UI elements. If the "stopped" option is selected,
+   * reinitializes the pipeline instead.
+   */
+  function updatePipelineSource() {
+    const sourceType =
+        sourceSelector.options[sourceSelector.selectedIndex].value;
+    if (!sourceType || !pipeline) {
+      initPipeline();
+    } else {
+      updatePipelineSourceIfSet();
+    }
+  }
+  sourceSelector.oninput = updatePipelineSource;
+  sourceSelector.disabled = false;
+
+  /**
+   * Updates the source visibility, if the source is already started.
+   */
+  function updatePipelineSourceVisibility() {
+    console.log(`[UI] Changed source visibility: ${
+        sourceVisibleCheckbox.checked ? 'added' : 'removed'}`);
+    if (pipeline) {
+      const source = pipeline.getSource();
+      if (source) {
+        source.setVisibility(sourceVisibleCheckbox.checked);
+      }
+    }
+  }
+  sourceVisibleCheckbox.oninput = updatePipelineSourceVisibility;
+  sourceVisibleCheckbox.disabled = false;
+
+  const transformSelector = /** @type {!HTMLSelectElement} */ (
+    document.getElementById('transformSelector'));
+  /**
+   * Updates the pipeline based on the current settings of the transformSelector
+   * UI element.
+   */
+  function updatePipelineTransform() {
+    const transformType =
+        transformSelector.options[transformSelector.selectedIndex].value;
+    console.log(`[UI] Selected transform: ${transformType}`);
+    switch (transformType) {
+      case 'webgl':
+        pipeline.updateTransform(new WebGLTransform());
+        break;
+      case 'canvas2d':
+        pipeline.updateTransform(new CanvasTransform());
+        break;
+      case 'drop':
+        // Defined in simple-transforms.js.
+        pipeline.updateTransform(new DropTransform());
+        break;
+      case 'delay':
+        // Defined in simple-transforms.js.
+        pipeline.updateTransform(new DelayTransform());
+        break;
+      default:
+        alert(`unknown transform ${transformType}`);
+        break;
+    }
+  }
+  transformSelector.oninput = updatePipelineTransform;
+  transformSelector.disabled = false;
+
+  const sinkSelector = (/** @type {!HTMLSelectElement} */ (
+    document.getElementById('sinkSelector')));
+  /**
+   * Updates the pipeline based on the current settings of the sinkSelector UI
+   * element.
+   */
+  function updatePipelineSink() {
+    const sinkType = sinkSelector.options[sinkSelector.selectedIndex].value;
+    console.log(`[UI] Selected sink: ${sinkType}`);
+    switch (sinkType) {
+      case 'video':
+        pipeline.updateSink(new VideoSink());
+        break;
+      case 'pc':
+        pipeline.updateSink(new PeerConnectionSink());
+        break;
+      default:
+        alert(`unknown sink ${sinkType}`);
+        break;
+    }
+  }
+  sinkSelector.oninput = updatePipelineSink;
+  sinkSelector.disabled = false;
+
+  /**
+   * Initializes/reinitializes the pipeline. Called on page load and after the
+   * user chooses to stop the video source.
+   */
+  function initPipeline() {
+    if (pipeline) pipeline.destroy();
+    pipeline = new Pipeline();
+    debug = {pipeline};
+    updatePipelineSourceIfSet();
+    updatePipelineTransform();
+    updatePipelineSink();
+    console.log(
+        '[initPipeline] Created new Pipeline.', 'debug.pipeline =', pipeline);
+  }
+}
+
+window.onload = initUI;
+'use strict';
+
+/**
+ * Sends a MediaStream to one end of an RTCPeerConnection and provides the
+ * remote end as the resulting MediaStream.
+ * In an actual video calling app, the two RTCPeerConnection objects would be
+ * instantiated on different devices. However, in this sample, both sides of the
+ * peer connection are local to allow the sample to be self-contained.
+ * For more detailed samples using RTCPeerConnection, take a look at
+ * https://webrtc.github.io/samples/.
+ */
+class PeerConnectionPipe { // eslint-disable-line no-unused-vars
+  /**
+   * @param {!MediaStream} inputStream stream to pipe over the peer connection
+   * @param {string} debugPath the path to this object from the debug global var
+   */
+  constructor(inputStream, debugPath) {
+    /**
+     * @private @const {!RTCPeerConnection} the calling side of the peer
+     *     connection, connected to inputStream_.
+     */
+    this.caller_ = new RTCPeerConnection(null);
+    /**
+     * @private @const {!RTCPeerConnection} the answering side of the peer
+     *     connection, providing the stream returned by getMediaStream.
+     */
+    this.callee_ = new RTCPeerConnection(null);
+    /** @private {string} */
+    this.debugPath_ = debugPath;
+    /**
+     * @private @const {!Promise<!MediaStream>} the stream containing tracks
+     *     from callee_, returned by getMediaStream.
+     */
+    this.outputStreamPromise_ = this.init_(inputStream);
+  }
+  /**
+   * Sets the path to this object from the debug global var.
+   * @param {string} path
+   */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+  }
+  /**
+   * @param {!MediaStream} inputStream stream to pipe over the peer connection
+   * @return {!Promise<!MediaStream>}
+   * @private
+   */
+  async init_(inputStream) {
+    console.log(
+        '[PeerConnectionPipe] Initiating peer connection.',
+        `${this.debugPath_} =`, this);
+    this.caller_.onicecandidate = (/** !RTCPeerConnectionIceEvent*/ event) => {
+      if (event.candidate) this.callee_.addIceCandidate(event.candidate);
+    };
+    this.callee_.onicecandidate = (/** !RTCPeerConnectionIceEvent */ event) => {
+      if (event.candidate) this.caller_.addIceCandidate(event.candidate);
+    };
+    const outputStream = new MediaStream();
+    const receiverStreamPromise = new Promise(resolve => {
+      this.callee_.ontrack = (/** !RTCTrackEvent */ event) => {
+        outputStream.addTrack(event.track);
+        if (outputStream.getTracks().length == inputStream.getTracks().length) {
+          resolve(outputStream);
+        }
+      };
+    });
+    inputStream.getTracks().forEach(track => {
+      this.caller_.addTransceiver(track, {direction: 'sendonly'});
+    });
+    await this.caller_.setLocalDescription();
+    await this.callee_.setRemoteDescription(
+        /** @type {!RTCSessionDescription} */ (this.caller_.localDescription));
+    await this.callee_.setLocalDescription();
+    await this.caller_.setRemoteDescription(
+        /** @type {!RTCSessionDescription} */ (this.callee_.localDescription));
+    await receiverStreamPromise;
+    console.log(
+        '[PeerConnectionPipe] Peer connection established.',
+        `${this.debugPath_}.caller_ =`, this.caller_,
+        `${this.debugPath_}.callee_ =`, this.callee_);
+    return receiverStreamPromise;
+  }
+
+  /**
+   * Provides the MediaStream that has been piped through a peer connection.
+   * @return {!Promise<!MediaStream>}
+   */
+  getOutputStream() {
+    return this.outputStreamPromise_;
+  }
+
+  /** Frees any resources used by this object. */
+  destroy() {
+    console.log('[PeerConnectionPipe] Closing peer connection.');
+    this.caller_.close();
+    this.callee_.close();
+  }
+}
+'use strict';
+
+/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js
+/* global VideoSink */ // defined in video-sink.js
+
+/**
+ * Sends the transformed video to one end of an RTCPeerConnection and displays
+ * the remote end in a video element. In this sample, a PeerConnectionSink
+ * represents processing the local user's camera input using a
+ * MediaStreamTrackProcessor before sending it to a remote video call
+ * participant. Contrast with a PeerConnectionSource.
+ * @implements {MediaStreamSink} in pipeline.js
+ */
+class PeerConnectionSink { // eslint-disable-line no-unused-vars
+  constructor() {
+    /**
+     * @private @const {!VideoSink} manages displaying the video stream in the
+     *     page
+     */
+    this.videoSink_ = new VideoSink();
+    /**
+     * @private {?PeerConnectionPipe} handles piping the MediaStream through an
+     *     RTCPeerConnection
+     */
+    this.pipe_ = null;
+    /** @private {string} */
+    this.debugPath_ = 'debug.pipeline.sink_';
+    this.videoSink_.setDebugPath(`${this.debugPath_}.videoSink_`);
+  }
+
+  /** @override */
+  async setMediaStream(stream) {
+    console.log(
+        '[PeerConnectionSink] Setting peer connection sink stream.', stream);
+    if (this.pipe_) this.pipe_.destroy();
+    this.pipe_ = new PeerConnectionPipe(stream, `${this.debugPath_}.pipe_`);
+    const pipedStream = await this.pipe_.getOutputStream();
+    console.log(
+        '[PeerConnectionSink] Received callee peer connection stream.',
+        pipedStream);
+    await this.videoSink_.setMediaStream(pipedStream);
+  }
+
+  /** @override */
+  destroy() {
+    this.videoSink_.destroy();
+    if (this.pipe_) this.pipe_.destroy();
+  }
+}
+'use strict';
+
+/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js
+/* global VideoMirrorHelper */ // defined in video-mirror-helper.js
+
+/**
+ * Sends the original source video to one end of an RTCPeerConnection and
+ * provides the remote end as the final source.
+ * In this sample, a PeerConnectionSource represents receiving video from a
+ * remote participant and locally processing it using a
+ * MediaStreamTrackProcessor before displaying it on the screen. Contrast with a
+ * PeerConnectionSink.
+ * @implements {MediaStreamSource} in pipeline.js
+ */
+class PeerConnectionSource { // eslint-disable-line no-unused-vars
+  /**
+   * @param {!MediaStreamSource} originalSource original stream source, whose
+   *     output is sent over the peer connection
+   */
+  constructor(originalSource) {
+    /**
+     * @private @const {!VideoMirrorHelper} manages displaying the video stream
+     *     in the page
+     */
+    this.videoMirrorHelper_ = new VideoMirrorHelper();
+    /**
+     * @private @const {!MediaStreamSource} original stream source, whose output
+     *     is sent on the sender peer connection. In an actual video calling
+     *     app, this stream would be generated from the remote participant's
+     *     camera. However, in this sample, both sides of the peer connection
+     *     are local to allow the sample to be self-contained.
+     */
+    this.originalStreamSource_ = originalSource;
+    /**
+     * @private {?PeerConnectionPipe} handles piping the MediaStream through an
+     *     RTCPeerConnection
+     */
+    this.pipe_ = null;
+    /** @private {string} */
+    this.debugPath_ = '<unknown>';
+  }
+  /** @override */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+    this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);
+    this.originalStreamSource_.setDebugPath(`${path}.originalStreamSource_`);
+    if (this.pipe_) this.pipe_.setDebugPath(`${path}.pipe_`);
+  }
+  /** @override */
+  setVisibility(visible) {
+    this.videoMirrorHelper_.setVisibility(visible);
+  }
+
+  /** @override */
+  async getMediaStream() {
+    if (this.pipe_) return this.pipe_.getOutputStream();
+
+    console.log(
+        '[PeerConnectionSource] Obtaining original source media stream.',
+        `${this.debugPath_}.originalStreamSource_ =`,
+        this.originalStreamSource_);
+    const originalStream = await this.originalStreamSource_.getMediaStream();
+    this.pipe_ =
+        new PeerConnectionPipe(originalStream, `${this.debugPath_}.pipe_`);
+    const outputStream = await this.pipe_.getOutputStream();
+    console.log(
+        '[PeerConnectionSource] Received callee peer connection stream.',
+        outputStream);
+    this.videoMirrorHelper_.setStream(outputStream);
+    return outputStream;
+  }
+
+  /** @override */
+  destroy() {
+    this.videoMirrorHelper_.destroy();
+    if (this.pipe_) this.pipe_.destroy();
+    this.originalStreamSource_.destroy();
+  }
+}
+'use strict';
+
+/* global createProcessedMediaStreamTrack */ // defined in main.js
+
+/**
+ * Wrapper around createProcessedMediaStreamTrack to apply transform to a
+ * MediaStream.
+ * @param {!MediaStream} sourceStream the video stream to be transformed. The
+ *     first video track will be used.
+ * @param {!FrameTransformFn} transform the transform to apply to the
+ *     sourceStream.
+ * @param {!AbortSignal} signal can be used to stop processing
+ * @return {!MediaStream} holds a single video track of the transformed video
+ *     frames
+ */
+function createProcessedMediaStream(sourceStream, transform, signal) {
+  // For this sample, we're only dealing with video tracks.
+  /** @type {!MediaStreamTrack} */
+  const sourceTrack = sourceStream.getVideoTracks()[0];
+
+  const processedTrack =
+      createProcessedMediaStreamTrack(sourceTrack, transform, signal);
+
+  // Create a new MediaStream to hold our processed track.
+  const processedStream = new MediaStream();
+  processedStream.addTrack(processedTrack);
+
+  return processedStream;
+}
+
+/**
+ * Interface implemented by all video sources the user can select. A common
+ * interface allows the user to choose a source independently of the transform
+ * and sink.
+ * @interface
+ */
+class MediaStreamSource { // eslint-disable-line no-unused-vars
+  /**
+   * Sets the path to this object from the debug global var.
+   * @param {string} path
+   */
+  setDebugPath(path) {}
+  /**
+   * Indicates if the source video should be mirrored/displayed on the page. If
+   * false (the default), any element producing frames will not be a child of
+   * the document.
+   * @param {boolean} visible whether to add the raw source video to the page
+   */
+  setVisibility(visible) {}
+  /**
+   * Initializes and returns the MediaStream for this source.
+   * @return {!Promise<!MediaStream>}
+   */
+  async getMediaStream() {}
+  /** Frees any resources used by this object. */
+  destroy() {}
+}
+
+/**
+ * Interface implemented by all video transforms that the user can select. A
+ * common interface allows the user to choose a transform independently of the
+ * source and sink.
+ * @interface
+ */
+class FrameTransform { // eslint-disable-line no-unused-vars
+  /** Initializes state that is reused across frames. */
+  async init() {}
+  /**
+   * Applies the transform to frame. Queues the output frame (if any) using the
+   * controller.
+   * @param {!VideoFrame} frame the input frame
+   * @param {!TransformStreamDefaultController<!VideoFrame>} controller
+   */
+  async transform(frame, controller) {}
+  /** Frees any resources used by this object. */
+  destroy() {}
+}
+
+/**
+ * Interface implemented by all video sinks that the user can select. A common
+ * interface allows the user to choose a sink independently of the source and
+ * transform.
+ * @interface
+ */
+class MediaStreamSink { // eslint-disable-line no-unused-vars
+  /**
+   * @param {!MediaStream} stream
+   */
+  async setMediaStream(stream) {}
+  /** Frees any resources used by this object. */
+  destroy() {}
+}
+
+/**
+ * Assembles a MediaStreamSource, FrameTransform, and MediaStreamSink together.
+ */
+class Pipeline { // eslint-disable-line no-unused-vars
+  constructor() {
+    /** @private {?MediaStreamSource} set by updateSource*/
+    this.source_ = null;
+    /** @private {?FrameTransform} set by updateTransform */
+    this.frameTransform_ = null;
+    /** @private {?MediaStreamSink} set by updateSink */
+    this.sink_ = null;
+    /** @private {!AbortController} may used to stop all processing */
+    this.abortController_ = new AbortController();
+    /**
+     * @private {?MediaStream} set in maybeStartPipeline_ after all of source_,
+     *     frameTransform_, and sink_ are set
+     */
+    this.processedStream_ = null;
+  }
+
+  /** @return {?MediaStreamSource} */
+  getSource() {
+    return this.source_;
+  }
+
+  /**
+   * Sets a new source for the pipeline.
+   * @param {!MediaStreamSource} mediaStreamSource
+   */
+  async updateSource(mediaStreamSource) {
+    if (this.source_) {
+      this.abortController_.abort();
+      this.abortController_ = new AbortController();
+      this.source_.destroy();
+      this.processedStream_ = null;
+    }
+    this.source_ = mediaStreamSource;
+    this.source_.setDebugPath('debug.pipeline.source_');
+    console.log(
+        '[Pipeline] Updated source.',
+        'debug.pipeline.source_ = ', this.source_);
+    await this.maybeStartPipeline_();
+  }
+
+  /** @private */
+  async maybeStartPipeline_() {
+    if (this.processedStream_ || !this.source_ || !this.frameTransform_ ||
+        !this.sink_) {
+      return;
+    }
+    const sourceStream = await this.source_.getMediaStream();
+    await this.frameTransform_.init();
+    try {
+      this.processedStream_ = createProcessedMediaStream(
+          sourceStream, async (frame, controller) => {
+            if (this.frameTransform_) {
+              await this.frameTransform_.transform(frame, controller);
+            }
+          }, this.abortController_.signal);
+    } catch (e) {
+      this.destroy();
+      return;
+    }
+    await this.sink_.setMediaStream(this.processedStream_);
+    console.log(
+        '[Pipeline] Pipeline started.',
+        'debug.pipeline.abortController_ =', this.abortController_);
+  }
+
+  /**
+   * Sets a new transform for the pipeline.
+   * @param {!FrameTransform} frameTransform
+   */
+  async updateTransform(frameTransform) {
+    if (this.frameTransform_) this.frameTransform_.destroy();
+    this.frameTransform_ = frameTransform;
+    console.log(
+        '[Pipeline] Updated frame transform.',
+        'debug.pipeline.frameTransform_ = ', this.frameTransform_);
+    if (this.processedStream_) {
+      await this.frameTransform_.init();
+    } else {
+      await this.maybeStartPipeline_();
+    }
+  }
+
+  /**
+   * Sets a new sink for the pipeline.
+   * @param {!MediaStreamSink} mediaStreamSink
+   */
+  async updateSink(mediaStreamSink) {
+    if (this.sink_) this.sink_.destroy();
+    this.sink_ = mediaStreamSink;
+    console.log(
+        '[Pipeline] Updated sink.', 'debug.pipeline.sink_ = ', this.sink_);
+    if (this.processedStream_) {
+      await this.sink_.setMediaStream(this.processedStream_);
+    } else {
+      await this.maybeStartPipeline_();
+    }
+  }
+
+  /** Frees any resources used by this object. */
+  destroy() {
+    console.log('[Pipeline] Destroying Pipeline');
+    this.abortController_.abort();
+    if (this.source_) this.source_.destroy();
+    if (this.frameTransform_) this.frameTransform_.destroy();
+    if (this.sink_) this.sink_.destroy();
+  }
+}
+'use strict';
+
+/**
+ * Drops frames at random.
+ * @implements {FrameTransform} in pipeline.js
+ */
+class DropTransform { // eslint-disable-line no-unused-vars
+  /** @override */
+  async init() {}
+  /** @override */
+  async transform(frame, controller) {
+    if (Math.random() < 0.5) {
+      controller.enqueue(frame);
+    } else {
+      frame.destroy();
+    }
+  }
+  /** @override */
+  destroy() {}
+}
+
+/**
+ * Delays all frames by 100ms.
+ * @implements {FrameTransform} in pipeline.js
+ */
+class DelayTransform { // eslint-disable-line no-unused-vars
+  /** @override */
+  async init() {}
+  /** @override */
+  async transform(frame, controller) {
+    await new Promise(resolve => setTimeout(resolve, 100));
+    controller.enqueue(frame);
+  }
+  /** @override */
+  destroy() {}
+}
+'use strict';
+
+/**
+ * Helper to display a MediaStream in an HTMLVideoElement, based on the
+ * visibility setting.
+ */
+class VideoMirrorHelper { // eslint-disable-line no-unused-vars
+  constructor() {
+    /** @private {boolean} */
+    this.visibility_ = false;
+    /** @private {?MediaStream} the stream to display */
+    this.stream_ = null;
+    /**
+     * @private {?HTMLVideoElement} video element mirroring the camera stream.
+     *    Set if visibility_ is true and stream_ is set.
+     */
+    this.video_ = null;
+    /** @private {string} */
+    this.debugPath_ = '<unknown>';
+  }
+  /**
+   * Sets the path to this object from the debug global var.
+   * @param {string} path
+   */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+  }
+  /**
+   * Indicates if the video should be mirrored/displayed on the page.
+   * @param {boolean} visible whether to add the video from the source stream to
+   *     the page
+   */
+  setVisibility(visible) {
+    this.visibility_ = visible;
+    if (this.video_ && !this.visibility_) {
+      this.video_.parentNode.removeChild(this.video_);
+      this.video_ = null;
+    }
+    this.maybeAddVideoElement_();
+  }
+
+  /**
+   * @param {!MediaStream} stream
+   */
+  setStream(stream) {
+    this.stream_ = stream;
+    this.maybeAddVideoElement_();
+  }
+
+  /** @private */
+  maybeAddVideoElement_() {
+    if (!this.video_ && this.visibility_ && this.stream_) {
+      this.video_ =
+        /** @type {!HTMLVideoElement} */ (document.createElement('video'));
+      console.log(
+          '[VideoMirrorHelper] Adding source video mirror.',
+          `${this.debugPath_}.video_ =`, this.video_);
+      this.video_.classList.add('video', 'sourceVideo');
+      this.video_.srcObject = this.stream_;
+      const outputVideo = document.getElementById('outputVideo');
+      outputVideo.parentNode.insertBefore(this.video_, outputVideo);
+      this.video_.play();
+    }
+  }
+
+  /** Frees any resources used by this object. */
+  destroy() {
+    if (this.video_) {
+      this.video_.pause();
+      this.video_.srcObject = null;
+      this.video_.parentNode.removeChild(this.video_);
+    }
+  }
+}
+'use strict';
+
+/**
+ * Displays the output stream in a video element.
+ * @implements {MediaStreamSink} in pipeline.js
+ */
+class VideoSink { // eslint-disable-line no-unused-vars
+  constructor() {
+    /**
+     * @private {?HTMLVideoElement} output video element
+     */
+    this.video_ = null;
+    /** @private {string} */
+    this.debugPath_ = 'debug.pipeline.sink_';
+  }
+  /**
+   * Sets the path to this object from the debug global var.
+   * @param {string} path
+   */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+  }
+  /** @override */
+  async setMediaStream(stream) {
+    console.log('[VideoSink] Setting sink stream.', stream);
+    if (!this.video_) {
+      this.video_ =
+        /** @type {!HTMLVideoElement} */ (document.createElement('video'));
+      this.video_.classList.add('video', 'sinkVideo');
+      document.getElementById('outputVideo').appendChild(this.video_);
+      console.log(
+          '[VideoSink] Added video element to page.',
+          `${this.debugPath_}.video_ =`, this.video_);
+    }
+    this.video_.srcObject = stream;
+    this.video_.play();
+  }
+  /** @override */
+  destroy() {
+    if (this.video_) {
+      console.log('[VideoSink] Stopping sink video');
+      this.video_.pause();
+      this.video_.srcObject = null;
+      this.video_.parentNode.removeChild(this.video_);
+    }
+  }
+}
+'use strict';
+
+/**
+ * Decodes and plays a video.
+ * @implements {MediaStreamSource} in pipeline.js
+ */
+class VideoSource { // eslint-disable-line no-unused-vars
+  constructor() {
+    /** @private {boolean} */
+    this.visibility_ = false;
+    /** @private {?HTMLVideoElement} video element providing the MediaStream */
+    this.video_ = null;
+    /**
+     * @private {?Promise<!MediaStream>} a Promise that resolves to the
+     *     MediaStream from captureStream. Set iff video_ is set.
+     */
+    this.stream_ = null;
+    /** @private {string} */
+    this.debugPath_ = '<unknown>';
+  }
+  /** @override */
+  setDebugPath(path) {
+    this.debugPath_ = path;
+  }
+  /** @override */
+  setVisibility(visible) {
+    this.visibility_ = visible;
+    if (this.video_) {
+      this.updateVideoVisibility();
+    }
+  }
+  /** @private */
+  updateVideoVisibility() {
+    if (this.video_.parentNode && !this.visibility_) {
+      if (!this.video_.paused) {
+        // Video playback is automatically paused when the element is removed
+        // from the DOM. That is not the behavior we want.
+        this.video_.onpause = async () => {
+          this.video_.onpause = null;
+          await this.video_.play();
+        };
+      }
+      this.video_.parentNode.removeChild(this.video_);
+    } else if (!this.video_.parentNode && this.visibility_) {
+      console.log(
+          '[VideoSource] Adding source video element to page.',
+          `${this.debugPath_}.video_ =`, this.video_);
+      const outputVideo = document.getElementById('outputVideo');
+      outputVideo.parentNode.insertBefore(this.video_, outputVideo);
+    }
+  }
+  /** @override */
+  async getMediaStream() {
+    if (this.stream_) return this.stream_;
+
+    console.log('[VideoSource] Loading video');
+
+    this.video_ =
+      /** @type {!HTMLVideoElement} */ (document.createElement('video'));
+    this.video_.classList.add('video', 'sourceVideo');
+    this.video_.controls = true;
+    this.video_.loop = true;
+    this.video_.muted = true;
+    // All browsers that support insertable streams also support WebM/VP8.
+    this.video_.src = 'road_trip_640_480.mp4';
+    this.video_.load();
+    this.video_.play();
+    this.updateVideoVisibility();
+    this.stream_ = new Promise((resolve, reject) => {
+      this.video_.oncanplay = () => {
+        if (!resolve || !reject) return;
+        console.log('[VideoSource] Obtaining video capture stream');
+        if (this.video_.captureStream) {
+          resolve(this.video_.captureStream());
+        } else if (this.video_.mozCaptureStream) {
+          resolve(this.video_.mozCaptureStream());
+        } else {
+          const e = new Error('Stream capture is not supported');
+          console.error(e);
+          reject(e);
+        }
+        resolve = null;
+        reject = null;
+      };
+    });
+    await this.stream_;
+    console.log(
+        '[VideoSource] Received source video stream.',
+        `${this.debugPath_}.stream_ =`, this.stream_);
+    return this.stream_;
+  }
+  /** @override */
+  destroy() {
+    if (this.video_) {
+      console.log('[VideoSource] Stopping source video');
+      this.video_.pause();
+      if (this.video_.parentNode) {
+        this.video_.parentNode.removeChild(this.video_);
+      }
+    }
+  }
+}
+'use strict';
+
+/**
+ * Applies a warp effect using WebGL.
+ * @implements {FrameTransform} in pipeline.js
+ */
+class WebGLTransform { // eslint-disable-line no-unused-vars
+  constructor() {
+    // All fields are initialized in init()
+    /** @private {?OffscreenCanvas} canvas used to create the WebGL context */
+    this.canvas_ = null;
+    /** @private {?WebGLRenderingContext} */
+    this.gl_ = null;
+    /** @private {?WebGLUniformLocation} location of inSampler */
+    this.sampler_ = null;
+    /** @private {?WebGLProgram} */
+    this.program_ = null;
+    /** @private {?WebGLTexture} input texture */
+    this.texture_ = null;
+    /** @private {string} */
+    this.debugPath_ = 'debug.pipeline.frameTransform_';
+  }
+  /** @override */
+  async init() {
+    console.log('[WebGLTransform] Initializing WebGL.');
+    this.canvas_ = new OffscreenCanvas(1, 1);
+    const gl = /** @type {?WebGLRenderingContext} */ (
+      this.canvas_.getContext('webgl'));
+    if (!gl) {
+      alert(
+          'Failed to create WebGL context. Check that WebGL is supported ' +
+          'by your browser and hardware.');
+      return;
+    }
+    this.gl_ = gl;
+    const vertexShader = this.loadShader_(gl.VERTEX_SHADER, `
+      precision mediump float;
+      attribute vec3 g_Position;
+      attribute vec2 g_TexCoord;
+      varying vec2 texCoord;
+      void main() {
+        gl_Position = vec4(g_Position, 1.0);
+        texCoord = g_TexCoord;
+      }`);
+    const fragmentShader = this.loadShader_(gl.FRAGMENT_SHADER, `
+      precision mediump float;
+      varying vec2 texCoord;
+      uniform sampler2D inSampler;
+      void main(void) {
+        float boundary = distance(texCoord, vec2(0.5)) - 0.2;
+        if (boundary < 0.0) {
+          gl_FragColor = texture2D(inSampler, texCoord);
+        } else {
+          // Rotate the position
+          float angle = 2.0 * boundary;
+          vec2 rotation = vec2(sin(angle), cos(angle));
+          vec2 fromCenter = texCoord - vec2(0.5);
+          vec2 rotatedPosition = vec2(
+            fromCenter.x * rotation.y + fromCenter.y * rotation.x,
+            fromCenter.y * rotation.y - fromCenter.x * rotation.x) + vec2(0.5);
+          gl_FragColor = texture2D(inSampler, rotatedPosition);
+        }
+      }`);
+    if (!vertexShader || !fragmentShader) return;
+    // Create the program object
+    const programObject = gl.createProgram();
+    gl.attachShader(programObject, vertexShader);
+    gl.attachShader(programObject, fragmentShader);
+    // Link the program
+    gl.linkProgram(programObject);
+    // Check the link status
+    const linked = gl.getProgramParameter(programObject, gl.LINK_STATUS);
+    if (!linked) {
+      const infoLog = gl.getProgramInfoLog(programObject);
+      gl.deleteProgram(programObject);
+      throw new Error(`Error linking program:\n${infoLog}`);
+    }
+    gl.deleteShader(vertexShader);
+    gl.deleteShader(fragmentShader);
+    this.sampler_ = gl.getUniformLocation(programObject, 'inSampler');
+    this.program_ = programObject;
+    // Bind attributes
+    const vertices = [1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
+    // Pass-through.
+    const txtcoords = [1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0];
+    // Mirror horizonally.
+    // const txtcoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
+    this.attributeSetFloats_('g_Position', 2, vertices);
+    this.attributeSetFloats_('g_TexCoord', 2, txtcoords);
+    // Initialize input texture
+    this.texture_ = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, this.texture_);
+    const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
+    gl.texImage2D(
+        gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+    console.log(
+        '[WebGLTransform] WebGL initialized.', `${this.debugPath_}.canvas_ =`,
+        this.canvas_, `${this.debugPath_}.gl_ =`, this.gl_);
+  }
+
+  /**
+   * Creates and compiles a WebGLShader from the provided source code.
+   * @param {number} type either VERTEX_SHADER or FRAGMENT_SHADER
+   * @param {string} shaderSrc
+   * @return {!WebGLShader}
+   * @private
+   */
+  loadShader_(type, shaderSrc) {
+    const gl = this.gl_;
+    const shader = gl.createShader(type);
+    // Load the shader source
+    gl.shaderSource(shader, shaderSrc);
+    // Compile the shader
+    gl.compileShader(shader);
+    // Check the compile status
+    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+      const infoLog = gl.getShaderInfoLog(shader);
+      gl.deleteShader(shader);
+      throw new Error(`Error compiling shader:\n${infoLog}`);
+    }
+    return shader;
+  }
+
+  /**
+   * Sets a floating point shader attribute to the values in arr.
+   * @param {string} attrName the name of the shader attribute to set
+   * @param {number} vsize the number of components of the shader attribute's
+   *   type
+   * @param {!Array<number>} arr the values to set
+   * @private
+   */
+  attributeSetFloats_(attrName, vsize, arr) {
+    const gl = this.gl_;
+    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW);
+    const attr = gl.getAttribLocation(this.program_, attrName);
+    gl.enableVertexAttribArray(attr);
+    gl.vertexAttribPointer(attr, vsize, gl.FLOAT, false, 0, 0);
+  }
+
+  /** @override */
+  async transform(frame, controller) {
+    const gl = this.gl_;
+    if (!gl || !this.canvas_) {
+      frame.destroy();
+      return;
+    }
+    const width = frame.displayWidth;
+    const height = frame.displayHeight;
+    if (this.canvas_.width !== width || this.canvas_.height !== height) {
+      this.canvas_.width = width;
+      this.canvas_.height = height;
+      gl.viewport(0, 0, width, height);
+    }
+    // VideoFrame.timestamp is technically optional, but that should never
+    // happen here.
+    // TODO(benjaminwagner): Follow up if we should change the spec so this is
+    // non-optional.
+    const timestamp = /** @type {number} */ (frame.timestamp);
+    const inputBitmap = await frame.createImageBitmap();
+    frame.destroy();
+    gl.activeTexture(gl.TEXTURE0);
+    gl.bindTexture(gl.TEXTURE_2D, this.texture_);
+    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+    gl.texImage2D(
+        gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, inputBitmap);
+    inputBitmap.close();
+    gl.useProgram(this.program_);
+    gl.uniform1i(this.sampler_, 0);
+    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+    gl.bindTexture(gl.TEXTURE_2D, null);
+    const outputBitmap = await createImageBitmap(this.canvas_);
+    const outputFrame = new VideoFrame(outputBitmap, {timestamp});
+    outputBitmap.close();
+    controller.enqueue(outputFrame);
+  }
+
+  /** @override */
+  destroy() {
+    if (this.gl_) {
+      console.log('[WebGLTransform] Forcing WebGL context to be lost.');
+      /** @type {!WEBGL_lose_context} */ (
+        this.gl_.getExtension('WEBGL_lose_context'))
+          .loseContext();
+    }
+  }
+}
diff --git a/ui/base/ime/chromeos/extension_ime_util.cc b/ui/base/ime/chromeos/extension_ime_util.cc
index 8cac3adc..9588354 100644
--- a/ui/base/ime/chromeos/extension_ime_util.cc
+++ b/ui/base/ime/chromeos/extension_ime_util.cc
@@ -159,13 +159,6 @@
          input_method_id.size() > kArcIMEPrefixLength + kExtensionIdLength;
 }
 
-bool IsMemberOfExtension(const std::string& input_method_id,
-                         const std::string& extension_id) {
-  return base::StartsWith(input_method_id,
-                          kExtensionIMEPrefix + extension_id,
-                          base::CompareCase::SENSITIVE);
-}
-
 bool IsKeyboardLayoutExtension(const std::string& input_method_id) {
   if (IsComponentExtensionIME(input_method_id))
     return base::StartsWith(GetComponentIDByInputMethodID(input_method_id),
diff --git a/ui/chromeos/file_manager_strings.grdp b/ui/chromeos/file_manager_strings.grdp
index e2badd0..fcc709b 100644
--- a/ui/chromeos/file_manager_strings.grdp
+++ b/ui/chromeos/file_manager_strings.grdp
@@ -881,30 +881,6 @@
     An error occurred. Some items may not have been restored.
   </message>
 
-  <message name="IDS_FILE_BROWSER_COPY_PROGRESS_SUMMARY" desc="Summary message for multiple copying tasks above the progress bar.">
-    Copying...
-  </message>
-  <message name="IDS_FILE_BROWSER_MOVE_PROGRESS_SUMMARY" desc="Summary message for multiple moving tasks above the progress bar.">
-    Moving...
-  </message>
-  <message name="IDS_FILE_BROWSER_ZIP_PROGRESS_SUMMARY" desc="Summary message for multiple zipping tasks above the progress bar.">
-    Zipping...
-  </message>
-  <message name="IDS_FILE_BROWSER_DELETE_PROGRESS_SUMMARY" desc="Summary message for multiple deleting tasks above the progress bar.">
-    Deleting...
-  </message>
-  <message name="IDS_FILE_BROWSER_TRANSFER_PROGRESS_SUMMARY" desc="Summary message for mixed operation tasks above the progress bar. 'Transferring' is used here as a generic term for copying, moving, zipping, or deleting.">
-    Transferring...
-  </message>
-  <message name="IDS_FILE_BROWSER_SYNC_PROGRESS_SUMMARY" desc="Summary message for drive sync operation. This message can be shown without a progress bar.">
-    Syncing...
-  </message>
-  <message name="IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY" desc="Summary message for an error above the progress bar. This message may be placed at the next to ohter *_PROGERSS_SUMMARY messages.">
-    1 error.
-  </message>
-  <message name="IDS_FILE_BROWSER_ERROR_PROGRESS_SUMMARY_PLURAL" desc="Summary message for multiple errors above the progress bar. This message may be placed at the next to ohter *_PROGERSS_SUMMARY messages.">
-    <ph name="COUNT">$1<ex>10</ex></ph> errors.
-  </message>
   <message name="IDS_FILE_BROWSER_PERCENT_COMPLETE" desc="Percentage of file operation that is complete in a progress message.">
     <ph name="PERCENT">$1<ex>10</ex></ph>% complete.
   </message>
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index bb08a017..8433ce0 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -322,12 +322,8 @@
   void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
   }
-  void RecordManipulationTypeCounts(cc::ManipulationInfo info) override {}
-  void SendOverscrollEventFromImplSide(
-      const gfx::Vector2dF& overscroll_delta,
-      cc::ElementId scroll_latched_element_id) override {}
-  void SendScrollEndEventFromImplSide(
-      cc::ElementId scroll_latched_element_id) override {}
+  void UpdateCompositorScrollState(
+      const cc::CompositorCommitData& commit_data) override {}
   void RequestNewLayerTreeFrameSink() override;
   void DidInitializeLayerTreeFrameSink() override {}
   void DidFailToInitializeLayerTreeFrameSink() override;
diff --git a/ui/file_manager/base/tools/modules.py b/ui/file_manager/base/tools/modules.py
new file mode 100755
index 0000000..123edf1f
--- /dev/null
+++ b/ui/file_manager/base/tools/modules.py
@@ -0,0 +1,774 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import re
+import sys
+import subprocess
+
+
+def get_file_lines(file_path):
+    '''Returns list of lines from a file. '\n' at the end of lines are
+    removed.'''
+    with open(file_path, 'r') as f:
+        file_lines = f.readlines()
+    return [l.rstrip() for l in file_lines]
+
+
+def save_file_lines(file_path, file_lines):
+    '''Saves file_lines in file pointed by `file_path`, '\n' at the end of
+    lines are added back.'''
+    with open(file_path, 'w') as f:
+        for line in file_lines:
+            f.write(line + '\n')
+
+
+def get_relative_dependency(path, dir_path):
+    '''Given a file path, returns formatted BUILD.gn dependency.
+
+    Parameters:
+        path: file path to format as relative dependency.
+        dir_path: directory from which the relative dependency is calculated.
+
+    Returns:
+        Formatted dependency.
+        The 3 cases illustrated below are handled:
+        - ":file_type.m" if returned if file_type.js is in dir_path.
+        - "metadata:metadata_model.m" is returned if metadata/metadata_model.js
+        is in dir_path.
+        - "//ui/file_manager/externs:volume_manager.m" is returned if
+        ui/file_manager/externs is not included in dir_path.
+    '''
+    split_path = path.split('/')
+    file_name = split_path.pop().replace('.js', '.m')
+    split_dir_path = dir_path.split('/')
+    while (len(split_path) > 0 and len(split_dir_path) > 0
+           and split_path[0] == split_dir_path[0]):
+        del split_path[0]
+        del split_dir_path[0]
+    if len(split_dir_path) == 0:
+        # The dependency is within dir_path.
+        return '/'.join(split_path) + ':' + file_name
+    else:
+        return '//' + re.sub(r"\/[a-zA-Z0-9_]+\.js", ":", path) + file_name
+
+
+def get_index_substr(file_lines, substr):
+    '''Finds first occurence of `substr` and returns its index in
+    `file_lines`.'''
+    for i, line in enumerate(file_lines):
+        if substr in line:
+            return i
+    return -1
+
+
+def get_end_of_copyright(file_lines):
+    '''Get index of last line of copyright (after checking that the section
+    exists).'''
+    index = get_index_substr(file_lines, 'Copyright 20')
+    if index < 0:
+        return -1
+    while index < len(file_lines) and file_lines[index].startswith('// '):
+        index += 1
+    return index - 1
+
+
+def get_end_of_file_overview(file_lines):
+    '''Get index of last line of file_overview (after checking that the section
+    exists).'''
+    index = get_index_substr(file_lines, '@fileoverview')
+    if index < 0:
+        return -1
+    while index < len(file_lines) and file_lines[index] != ' */':
+        index += 1
+    return index
+
+
+def get_last_index_of_line_starting_with(file_lines, substr):
+    '''Get last line starting with the given `substr`.'''
+    last_index = -1
+    for i, line in enumerate(file_lines):
+        if line.startswith(substr):
+            last_index = i
+    return last_index
+
+
+def get_end_of_build_imports(file_lines):
+    '''In BUILD.gn, gets index of last 'import' line at the beginning of the
+    file.'''
+    index = get_last_index_of_line_starting_with(file_lines, 'import("//')
+    if index < 0:
+        return get_end_of_copyright(file_lines)
+    return index
+
+
+def add_js_library(file_lines, file_name, dir_path):
+    '''Adds js_library target in BUILD.gn.'''
+    i = file_lines.index('js_library("%s") {' % (file_name))
+
+    # Check that the library does exist and hasn't already been converted.
+    if i < 0:
+        print 'Unable to find js_library for {}'.format(file_name)
+        return False
+    if 'js_library("%s.m") {' % (file_name) in file_lines:
+        print 'js_library for {}.m already added'.format(file_name)
+        return False
+
+    # Find the end of the library definition.
+    while i < len(file_lines) and file_lines[i] != '}':
+        i += 1
+    i += 1
+    if i == len(file_lines):
+        print 'reached end of file'
+        return False
+    new_lines = '''
+js_library("%s.m") {
+  sources = [ "$root_gen_dir/%s/%s.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}''' % (file_name, dir_path, file_name)
+    file_lines[i:i] = new_lines.split('\n')
+    return True
+
+
+def add_import_line(file_lines, variable, relative_path, is_unittest):
+    '''Adds import line (import {...} from '...') in JS file.'''
+    # Construct import line.
+    import_line = 'import ' if is_unittest else '// #import '
+
+    # Check: existing relative path.
+    i = get_index_substr(file_lines, relative_path)
+    if i >= 0 and '}' in file_lines[i]:
+        if variable + '}' in file_lines[i] or variable + ',' in file_lines[i]:
+            return
+        split_line = file_lines[i].split('}')
+        file_lines[i] = '%s, %s}%s' % (split_line[0], variable, split_line[1])
+        return
+    import_line += "{%s} from '%s';" % (variable, relative_path)
+
+    if import_line in file_lines:
+        return
+
+    # Add clang-format off/on if necessary.
+    index = 0
+    if '// clang-format off' in file_lines:
+        index = file_lines.index('// clang-format off')
+    else:
+        # Go to the end of copyright and fileoverview.
+        index = get_end_of_file_overview(file_lines)
+        if index < 0:
+            index = get_end_of_copyright(file_lines)
+            if (index < 0):
+                index = 0
+        index += 1
+        if len(import_line) > 80:
+            index += 1
+            file_lines.insert(index, '// clang-format off')
+
+            # Go to the last existing import line.
+            last_import_line_index = get_last_index_of_line_starting_with(
+                file_lines, 'import ')
+            if last_import_line_index >= 0:
+                index = last_import_line_index
+            else:
+                file_lines.insert(index + 1, '')
+            file_lines.insert(index + 1, '// clang-format off')
+        elif 'import ' not in file_lines[index + 1]:
+            file_lines.insert(index, '')
+
+    # Add import line.
+    file_lines.insert(index + 1, import_line)
+
+
+def add_namespace_rewrite(build_gn_path):
+    build_file_lines = get_file_lines(build_gn_path)
+
+    # Add import("//ui/webui/resources/js/cr.gni").
+    cr_gni = 'import("//ui/webui/resources/js/cr.gni")'
+    modulizer_gni = 'import("//ui/webui/resources/tools/js_modulizer.gni")'
+    if not cr_gni in build_file_lines:
+        if not modulizer_gni in build_file_lines:
+            raise ValueError('"js_modulizer.gni" not found')
+        index = build_file_lines.index(modulizer_gni)
+        build_file_lines.insert(index, cr_gni)
+
+    # Add namespace_rewrites = cr_namespace_rewrites.
+    namespace_rewrite = '  namespace_rewrites = cr_namespace_rewrites'
+    if not namespace_rewrite in build_file_lines:
+        index = get_index_substr(build_file_lines,
+                                 'js_modulizer("modulize") {')
+        if index < 0:
+            print 'No modulize rule found'
+            return
+        while index < len(build_file_lines) and build_file_lines[index] != '':
+            index += 1
+        index -= 1
+        build_file_lines.insert(index, '')
+        build_file_lines.insert(index + 1, namespace_rewrite)
+
+    save_file_lines(build_gn_path, build_file_lines)
+
+
+def add_hide_third_party(build_gn_path):
+    build_file_lines = get_file_lines(build_gn_path)
+
+    hide_third_party = '    "hide_warnings_for=third_party/",'
+    prefix_replacement_line = 'browser_resolver_prefix_replacements='
+    if not hide_third_party in build_file_lines:
+        index = get_index_substr(build_file_lines, prefix_replacement_line)
+        if index < 0:
+            print 'prefix replacement not found in "js_test_gen_html_modules"'
+            return
+        build_file_lines.insert(index + 1, hide_third_party)
+
+    save_file_lines(build_gn_path, build_file_lines)
+
+
+def add_dependency(file_lines, section_first_line, list_name, dependency_line):
+    '''
+    Add dependency in BUILD.gn.
+    Parameters:
+        file_lines: lines of BUILD.gn file.
+        section_first_line: opening line of target to update.
+        list_name: name of the dependency list, deps', 'input_files' etc...
+        dependency_line: line to add to the dependency list to update.
+    '''
+    # Find a line that starts with deps.
+    if not section_first_line in file_lines:
+        print 'Unable to find ' + section_first_line
+        return False
+
+    # Return False if dependency already found.
+    if dependency_line in file_lines:
+        return False
+
+    # Find index of `list_name`. Get index of 'sources = [' in case `list_name`
+    # is not defined.
+    rule_index = file_lines.index(section_first_line)
+    sources_index = -1
+    insertion_index = -1
+    single_line_dependency_list = False
+
+    for i in range(rule_index, len(file_lines)):
+        if 'sources = [' in file_lines[i]:
+            # Jump to the end of the 'sources' list.
+            while not ']' in file_lines[i]:
+                i += 1
+            sources_index = i
+        if '  {} = '.format(list_name) in file_lines[i]:
+            # Dependency line found.
+            if file_lines[i].endswith(']'):
+                single_line_dependency_list = True
+
+            # Jump to the end of the dependency list.
+            while not ']' in file_lines[i]:
+                i += 1
+            insertion_index = i
+            break
+        if file_lines[i] == '}':
+            # End of build rule.
+            break
+
+    if insertion_index == -1:
+        # Add dependency line, after sources if possible.
+        index = sources_index + 1 if sources_index > 0 else rule_index + 1
+
+        # Define new list over 2 lines: 'list_name = [\n]'
+        file_lines.insert(index, '  {} = ['.format(list_name))
+        file_lines.insert(index + 1, '  ]')
+        insertion_index = index + 1
+
+    if single_line_dependency_list:
+        # If one-line dependency is found, rewrite it over 2 lines, as above.
+        existing_dependency = file_lines[insertion_index].split(' ')[-2]
+        file_lines[insertion_index] = '  {} = ['.format(list_name)
+        file_lines[insertion_index + 1] = '    {},'.format(existing_dependency)
+        file_lines[insertion_index + 2] = '  ]'
+
+        # Check for already imported dependency after reformatting.
+        if file_lines[insertion_index + 1] == dependency_line:
+            return False
+
+        insertion_index += 2
+
+    # Insert dependency.
+    file_lines.insert(insertion_index, dependency_line)
+    return True
+
+
+def update_build_gn_dependencies(dir_path, file_name, build_gn_path):
+    print 'Updating BUILD.gn dependencies for ' + file_name
+
+    # Get file contents.
+    file_lines = get_file_lines(build_gn_path)
+
+    # Edit file with modules-related targets.
+    import_gni = 'import("//ui/webui/resources/tools/js_modulizer.gni")'
+    if not import_gni in file_lines:
+        index = get_end_of_build_imports(file_lines) + 1
+        file_lines.insert(index, import_gni)
+        new_lines = '''
+js_modulizer("modulize") {
+  input_files = [
+  ]
+}'''
+        file_lines.extend(new_lines.split('\n'))
+        if not add_dependency(file_lines, 'group("closure_compile") {', 'deps',
+                              '    ":closure_compile_jsmodules",'):
+            return
+
+        # Add closure_compile_jsmodules rule.
+        index = get_index_substr(file_lines,
+                                 'js_type_check("closure_compile_module") {')
+        if index < 0:
+            print 'js_type_check("closure_compile_module") not found'
+            return
+        new_lines = '''\
+js_type_check("closure_compile_jsmodules") {
+  uses_js_modules = true
+  deps = [
+  ]
+}
+'''
+        file_lines[index:index] = new_lines.split('\n')
+    if not add_js_library(file_lines, file_name, dir_path):
+        return
+
+    # Add closure dependency.
+    if not add_dependency(
+            file_lines, 'js_type_check("closure_compile_jsmodules") {', 'deps',
+            '    ":{}.m",'.format(file_name)):
+        return
+
+    # Add 'modulize' dependency.
+    if not add_dependency(file_lines, 'js_modulizer("modulize") {',
+                          'input_files', '    "{}.js",'.format(file_name)):
+        return
+
+    # Save file contents.
+    save_file_lines(build_gn_path, file_lines)
+
+
+def update_buid_gn_unittest_dependencies(build_gn_path, js_file_path,
+                                         file_name):
+    '''
+    Rename _unittest.js to _unittest.m.js.
+    Update test URL in file_manager_jstest.cc.
+    Update BUILD.gn rules.
+    It is assumed that if we're converting [file]_unittest.js, [file].js has
+    been converted already.
+
+    Parameters:
+        build_gn_path: Path of BUILD.gn file used by the file be converted.
+        js_file_path: Path of the file to be converted
+                        (ui/file_manager/.../..._unittest.m.js)
+        file_name: Name of the file to be converted (..._unittest)
+    '''
+    print 'Updating BUILD.gn dependencies for ' + file_name
+
+    main_build_path = 'ui/file_manager/BUILD.gn'
+    file_manager_jstest_path = ('chrome/browser/chromeos/file_manager/'
+                                'file_manager_jstest.cc')
+
+    # Get file contents.
+    build_file_lines = get_file_lines(build_gn_path)
+    main_build_file_lines = get_file_lines(main_build_path)
+    file_manager_jstest_lines = get_file_lines(file_manager_jstest_path)
+
+    # Rename _unittest.js to _unittest.m.js.
+    original_js_file_path = js_file_path.replace('.m.js', '.js')
+    if not os.path.isfile(js_file_path):
+        if not os.path.isfile(original_js_file_path):
+            e = 'Could not find neither file {} or {}'.format(
+                js_file_path, original_js_file_path)
+            raise ValueError(e)
+        os.rename(original_js_file_path, js_file_path)
+
+    # Update chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+    # by updating RunTestURL for the unittest being converted.
+    # [...]_unittest_gen.html -> [...]_unittest.m_gen.html.
+    index = get_index_substr(file_manager_jstest_lines, file_name + '.m')
+    if index < 0:
+        index = get_index_substr(file_manager_jstest_lines, file_name)
+        if index < 0:
+            print 'file_manager_jstest.cc: {} not found'.format(file_name)
+        file_manager_jstest_lines[index] = file_manager_jstest_lines[
+            index].replace(file_name, file_name + '.m')
+
+    # Add "js_test_gen_html_modules" target.
+    build_rule = 'js_test_gen_html("js_test_gen_html_modules") {'
+    if not build_rule in build_file_lines:
+        if 'js_test_gen_html("js_test_gen_html") {' in build_file_lines:
+            index = build_file_lines.index(
+                'js_test_gen_html("js_test_gen_html") {') - 1
+            new_lines = r'''
+js_test_gen_html("js_test_gen_html_modules") {
+  deps = [
+  ]
+  js_module = true
+
+  closure_flags =
+      strict_error_checking_closure_args + [
+        "js_module_root=./gen/ui",
+        "js_module_root=../../ui",
+        "browser_resolver_prefix_replacements=\"chrome://test/=./\"",
+      ]
+}'''
+            build_file_lines[index:index] = new_lines.split('\n')
+        else:
+            e = '"js_test_gen_html" build target not found on {}'.format(
+                build_gn_path)
+            raise ValueError(e)
+
+    # Add ":js_test_gen_html_modules_type_check_auto", in closure_compile deps.
+    index = get_index_substr(build_file_lines,
+                             ':js_test_gen_html_modules_type_check_auto')
+    if index < 0:
+        index = get_index_substr(build_file_lines,
+                                 ':js_test_gen_html_type_check_auto')
+        if index < 0:
+            e = '"js_test_gen_html_type_check_auto" dependency not found'
+            raise ValueError(e)
+        else:
+            new_line = '    ":js_test_gen_html_modules_type_check_auto",'
+            build_file_lines.insert(index, new_line)
+
+    # In BUILD.gn, update js_unittest target to .m
+    js_unittest = 'js_unittest("%s") {' % (file_name)
+    js_unittest_m = 'js_unittest("%s.m") {' % (file_name)
+    if js_unittest in build_file_lines:
+        index = build_file_lines.index(js_unittest)
+        build_file_lines[index] = js_unittest_m
+        # Remove dependencies.
+        while index < len(
+                build_file_lines) and not 'deps =' in build_file_lines[index]:
+            index += 1
+        if ']' in build_file_lines[index]:
+            build_file_lines[index] = '  deps = ['
+            build_file_lines.insert(index, '  ]')
+        else:
+            index += 1
+            while index < len(
+                    build_file_lines) and not ']' in build_file_lines[index]:
+                del build_file_lines[index]
+    else:
+        if not js_unittest_m in build_file_lines:
+            print 'Unable to find "{}" target'.format(file_name)
+
+    # Move unittest dependency from js_test_gen_html to
+    # js_test_gen_html_modules.
+    index = get_index_substr(build_file_lines, '":{}",'.format(file_name))
+    if index >= 0:
+        del build_file_lines[index]
+    else:
+        single_line_deps = '  deps = [ ":{}" ]'.format(file_name)
+        if single_line_deps in build_file_lines:
+            index = build_file_lines.index(single_line_deps)
+            build_file_lines[index] = '  deps = []'
+
+    add_dependency(build_file_lines,
+                   'js_test_gen_html("js_test_gen_html_modules") {', 'deps',
+                   '    ":{}.m",'.format(file_name))
+    # Add dependency in ui/file_manager/BUILD.gn.
+    dir_path = os.path.dirname(js_file_path)
+    rel_dir_path = '/'.join(dir_path.split('/')[2:])
+    dependency_line = '    "{}:js_test_gen_html_modules",'.format(rel_dir_path)
+    if not dependency_line in main_build_file_lines:
+        if not add_dependency(main_build_file_lines,
+                              'group("unit_test_data") {', 'deps',
+                              dependency_line):
+            print 'unable to update ui/file_manager/BUILD.gn'
+
+    # Save file contents.
+    save_file_lines(build_gn_path, build_file_lines)
+    save_file_lines(main_build_path, main_build_file_lines)
+    save_file_lines(file_manager_jstest_path, file_manager_jstest_lines)
+
+
+def parse_compiler_output(compiler_output, build_gn_path, file_name):
+    '''Parse closure compile output and return list of token that are
+    undefined/undeclared (no duplicate).'''
+    print "Parsing closure compiler output"
+    with open(compiler_output, 'r') as f:
+        # Retrieve from the closure compile output the types that need to be
+        # imported.
+        variables_to_import = []
+        for line in f:
+            line = line.rstrip()
+            # Select from the output the lines that contain 'ERROR - []' and
+            # 'Unknown type '.
+            if 'ERROR - [' in line:
+                if not file_name in line:
+                    if '/third_party/' in line:
+                        add_hide_third_party(build_gn_path)
+                    else:
+                        print 'UNHANDLED ERROR (Other file): ' + line
+                elif 'Unknown type ' in line:
+                    unknown_type = line.split('Unknown type ')[1]
+                    # Select the main type by removing what follows the dot:
+                    # ThumbnailLoader.LoaderType -> ThumbnailLoader.
+                    unknown_type = unknown_type.split('.')[0]
+                    if not unknown_type in variables_to_import:
+                        variables_to_import.append(unknown_type)
+                elif 'variable ' in line and ' is undeclared' in line:
+                    if ' cr ' in line:  # namespace rewrite.
+                        add_namespace_rewrite(build_gn_path)
+                        continue
+                    unknown_type = line.split('variable ')[1]
+                    unknown_type = unknown_type.split(' is undeclared')[0]
+                    if not unknown_type in variables_to_import:
+                        variables_to_import.append(unknown_type)
+                else:
+                    print 'UNHANDLED ERROR: ' + line
+    return variables_to_import
+
+
+def find_dependencies(dir_path, build_gn_path, js_file_path,
+                      variables_to_import, is_unittest):
+    '''Finds dependencies and updates BUILD.gn and JS file to import these
+    dependencies.'''
+    # Get BUILD.gn and JS file contents.
+    build_file_lines = get_file_lines(build_gn_path)
+    js_file_lines = get_file_lines(js_file_path)
+
+    file_name = js_file_path.split('/').pop().replace('.js', '')
+
+    # Special case: classes that extend "cr.EventTarget".
+    index = get_index_substr(js_file_lines, ' extends cr.EventTarget')
+    if index >= 0:
+        if is_unittest:
+            js_file_lines[index] = js_file_lines[index].replace(
+                ' extends cr.EventTarget', ' extends EventTarget')
+            add_dependency(build_file_lines,
+                           'js_unittest("%s") {' % (file_name), 'deps',
+                           '    "//ui/webui/resources/js/cr:event_target.m",')
+        else:
+            add_dependency(build_file_lines,
+                           'js_library("%s.m") {' % (file_name), 'deps',
+                           '    "//ui/webui/resources/js/cr:event_target.m",')
+        add_import_line(js_file_lines, 'NativeEventTarget as EventTarget',
+                        'chrome://resources/js/cr/event_target.m.js',
+                        is_unittest)
+
+    # General case.
+    for variable in variables_to_import:
+        # First, handle special cases.
+        if is_unittest:
+            # "//chrome/test/data/webui:chai_assert".
+            out, _ = subprocess.Popen([
+                'egrep', '-R', 'export function {}\('.format(variable), '-l',
+                './chrome/test/data/webui/chai_assert.js'
+            ],
+                                      stdout=subprocess.PIPE).communicate()
+            if out.rstrip() != '':
+                add_dependency(build_file_lines,
+                               'js_unittest("%s") {' % (file_name), 'deps',
+                               '    "//chrome/test/data/webui:chai_assert",')
+                add_import_line(js_file_lines, variable,
+                                'chrome://test/chai_assert.js', is_unittest)
+                continue
+        else:
+            # "//ui/webui/resources/js".
+            out, _ = subprocess.Popen([
+                'egrep', '-R', '\/\* #export \*\/ \w+ {}\W'.format(variable),
+                '-l', './ui/webui/resources/js'
+            ],
+                                      stdout=subprocess.PIPE).communicate()
+            path = out.rstrip()
+            if path != '':
+                path = path.replace('./', '//').replace('.js', '.m')
+                dependency = ':'.join(path.rsplit(
+                    '/', 1))  # //ui/webui/resources/js:assert.m.
+                add_dependency(build_file_lines,
+                               'js_library("%s.m") {' % (file_name), 'deps',
+                               '    "{}",'.format(dependency))
+                path = path.replace('//ui/webui/', 'chrome://').replace(
+                    '.m', '.m.js')  # chrome://resources/js/assert.m.js.
+                add_import_line(js_file_lines, variable, path, is_unittest)
+                continue
+
+        # Look for exported variables.
+        out, _ = subprocess.Popen([
+            'egrep', '-R', '\/\* #export \*\/ \w+ {}\W'.format(variable), '-l',
+            './ui/file_manager/'
+        ],
+                                  stdout=subprocess.PIPE).communicate()
+        path = out.rstrip()
+        if path == '':
+            # Look for variables exported as wrapped objects
+            out, _ = subprocess.Popen([
+                'egrep', '-R',
+                '\/\* #export \*\/ \{%s\}' % variable, '-l',
+                './ui/file_manager/'
+            ],
+                                      stdout=subprocess.PIPE).communicate()
+            path = out.rstrip()
+            if path == '':
+                continue
+        if '\n' in path:
+            print '{}: export found in more than 1 file'.format(variable)
+            print path
+            continue
+
+        # Remove './' at the start.
+        path = path[2:]
+        relative_path = os.path.relpath(path, start=dir_path).replace(
+            '.js', '.m.js')
+        if not relative_path.startswith('../'):
+            relative_path = './' + relative_path
+        add_import_line(js_file_lines, variable, relative_path, is_unittest)
+        if is_unittest:
+            add_dependency(
+                build_file_lines, 'js_unittest("%s") {' % (file_name), 'deps',
+                '    "{}",'.format(get_relative_dependency(path, dir_path)))
+        else:
+            add_dependency(
+                build_file_lines, 'js_library("%s.m") {' % (file_name), 'deps',
+                '    "{}",'.format(get_relative_dependency(path, dir_path)))
+
+    # Save BUILD.gn and JS file contents.
+    save_file_lines(build_gn_path, build_file_lines)
+    save_file_lines(js_file_path, js_file_lines)
+
+
+def convert_js_file(js_file_path):
+    '''Add exports (/* #export */) and ignore 'use strict'.'''
+    file_lines = get_file_lines(js_file_path)
+
+    # Ignore 'use strict'.
+    index = get_index_substr(file_lines, "'use strict'")
+    if index >= 0:
+        file_lines[index] = file_lines[index].replace(
+            "'use strict'", "/* #ignore */ 'use strict'")
+
+    # Add exports.
+    for i, line in enumerate(file_lines):
+        # Export class.
+        if line.startswith('class '):
+            # Extract class name.
+            class_name = line.split(' ')[1]
+            # Make sure this class is not used locally.
+            if get_index_substr(file_lines, 'new ' + class_name) >= 0:
+                # Export only if the class name is the name of the file.
+                snake_case_name = ''.join([
+                    '_' + c.lower() if c.isupper() else c for c in class_name
+                ]).lstrip('_')
+                if not snake_case_name + '.js' == js_file_path.split('/')[-1]:
+                    continue
+            file_lines[i] = '/* #export */ ' + line
+        # Export variable in global namespace.
+        elif line.startswith('const ') or line.startswith(
+                'let ') or line.startswith('var '):
+            # Extract variable name.
+            variable_name = line.split(' ')[1]
+
+            # Check if the variable is further defined/extended in the file. If
+            # so, export it.
+            export_variable = False
+            for l in range(len(file_lines)):
+                if file_lines[l].startswith(variable_name + '.'):
+                    export_variable = True
+                    break
+            if not export_variable:
+                continue
+
+            # Check if export already added.
+            if get_index_substr(file_lines, '/* #export */ {%s}' %
+                                (variable_name)) >= 0:
+                continue
+
+            # Export variable in wrapping object at the end of the file.
+            new_lines = '''
+// eslint-disable-next-line semi,no-extra-semi
+/* #export */ {%s};''' % (variable_name)
+            file_lines.extend(new_lines.split('\n'))
+
+            # Check if @suppress already added.
+            if get_index_substr(file_lines, ' @suppress {uselessCode}') >= 0:
+                continue
+            index = get_end_of_file_overview(file_lines)
+            if index < 0:
+                index = get_end_of_copyright(file_lines)
+                if index < 0:
+                    index = 0
+                index += 1
+                new_lines = '''
+/**
+ * @fileoverview
+ * @suppress {uselessCode} Temporary suppress %s.
+ */''' % ('because of the line exporting')
+                file_lines[index:index] = new_lines.split('\n')
+            else:
+                while ' */' not in file_lines[index]:
+                    index += 1
+                new_line = (' * @suppress {uselessCode} Temporary suppress '
+                            'because of the line exporting.')
+                file_lines.insert(index, new_line)
+        # Export function.
+        elif line.startswith('function '):
+            if not get_index_substr(file_lines, ' {}('.format(function_name)):
+                # The function has to be used outside the file, so has to be
+                # exported.
+                file_lines[i] = '/* #export */ ' + line
+
+    # Save file contents.
+    save_file_lines(js_file_path, file_lines)
+
+
+def convert_unittest_file(js_file_path):
+    '''Add exports and remove 'use strict'.'''
+    file_lines = get_file_lines(js_file_path)
+
+    # Remove 'use strict'.
+    index = get_index_substr(file_lines, "'use strict'")
+    if index >= 0:
+        del file_lines[index]
+
+    # Add exports.
+    for i, line in enumerate(file_lines):
+        if line.startswith('function setUp()') or 'function test' in line:
+            file_lines[i] = 'export ' + file_lines[i]
+
+    # Save file contents.
+    save_file_lines(js_file_path, file_lines)
+
+
+def main():
+    # Get command line arguments.
+    js_file_path = sys.argv[1]
+    action = sys.argv[2]
+    if not js_file_path.endswith('.js'):
+        e = "Argument 1 {} isn't a JS file (expected .js extension)".format(
+            js_file_path)
+        raise ValueError(e)
+    dir_path = os.path.dirname(js_file_path)
+    build_gn_path = dir_path + '/BUILD.gn'
+    file_name = os.path.basename(js_file_path).replace('.js', '')
+    is_unittest = file_name.endswith('_unittest')
+    if is_unittest:
+        js_file_path = js_file_path.replace('.js', '.m.js')
+
+    if (action == 'generate'):
+        if is_unittest:
+            update_buid_gn_unittest_dependencies(build_gn_path, js_file_path,
+                                                 file_name)
+            convert_unittest_file(js_file_path)
+        else:
+            update_build_gn_dependencies(dir_path, file_name, build_gn_path)
+            convert_js_file(js_file_path)
+    elif (action == 'parse'):
+        compiler_output = sys.argv[3]
+        variables_to_import = parse_compiler_output(compiler_output,
+                                                    build_gn_path, file_name)
+        if variables_to_import == []:
+            return
+        find_dependencies(dir_path, build_gn_path, js_file_path,
+                          variables_to_import, is_unittest)
+
+
+print "-----modules.py-----"
+main()
+print "--------------------"
diff --git a/ui/file_manager/base/tools/modules.sh b/ui/file_manager/base/tools/modules.sh
new file mode 100755
index 0000000..68ce308
--- /dev/null
+++ b/ui/file_manager/base/tools/modules.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Copyright (c) 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Command usage:
+# ui/file_manager/base/tools/modules.sh out/Release ui/file_manager/
+#   file_manager/foreground/js/list_thumbnail_loader.js
+# ui/file_manager/base/tools/modules.sh out/Release ui/file_manager/
+#   file_manager/common/js/importer_common.js
+# ui/file_manager/base/tools/modules.sh out/Release ui/file_manager/
+#   file_manager/foreground/js/list_thumbnail_loader_unittest.js
+
+# Input: js file to convert.
+build_dir=$1;
+file_path=$2;
+dir=`dirname $file_path`;
+compiler_output="$build_dir/gen/ui/file_manager/base/tools/compiler_output.txt";
+
+# Create containing directory of `compiler_output` if doesn't exist.
+mkdir -p `dirname $compiler_output`;
+
+# Process files with Python.
+ui/file_manager/base/tools/modules.py $file_path 'generate';
+
+# Parse closure compiler output and update files until the message 'X error(s),
+# Y warning(s), Z% typed' doesn't change.
+prev_output=""
+new_output="."
+while [[ $prev_output != $new_output ]]; do
+  prev_output=$new_output;
+
+  # Run closure compiler and save output.
+  ninja -C $build_dir $dir:closure_compile 2>&1 | tee $compiler_output;
+
+  # Parse closure compiler output.
+  ui/file_manager/base/tools/modules.py $file_path 'parse' $compiler_output;
+
+  # Get number of errors from modules.txt.
+  new_output=$(cat $compiler_output | grep 'error(s)' );
+done;
+
+# Format files.
+git cl format --js;
+
+if [[ $new_output == "" ]]; then
+  echo "No closure compiler error found"
+else
+  echo "Final state: " $new_output;
+fi;
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 3b99a1d..eb945083 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -512,6 +512,15 @@
   if (use_ozone) {
     deps += [ "//ui/ozone" ]
   }
+
+  public_configs = []
+
+  # If the run-time search path isn't set properly when we use ANGLE with its
+  # Vulkan backend, it may end up finding the system libvulkan.so rather than
+  # the one built in the output directory
+  if ((is_linux || is_chromeos) && !is_component_build) {
+    public_configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
+  }
 }
 
 source_set("run_all_unittests") {
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index 4294b0bc..43d8f5b 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -398,6 +398,12 @@
       <message name="IDS_SENTENCE_END" desc="The symbol that is used to end a sentence.">
         .
       </message>
+      <message name="IDS_CONCAT_TWO_STRINGS_WITH_COMMA" desc="This string concatenates two other strings. In the English language, this particular concatenation is done via a comma and a whitespace in between the other strings. For example: '1 compromised password, 2 weak passwords'">
+        <ph name="TYPE_1">$1<ex>2 compromised passwords</ex></ph>, <ph name="TYPE_2">$2<ex>7 weak passwords</ex></ph>
+      </message>
+      <message name="IDS_CONCAT_TWO_STRINGS_WITH_PERIODS" desc="This string concatenates two other strings. In the English language, this particular concatenation is done via a period and a whitespace after the first string, and a period after the second string. For example: '1 compromised password. 2 weak passwords.'">
+        <ph name="TYPE_1">$1<ex>2 compromised passwords</ex></ph>. <ph name="TYPE_2">$2<ex>7 weak passwords</ex></ph>.
+      </message>
       <if expr="is_win or is_ios">
         <message name="IDS_APP_UNTITLED_SHORTCUT_FILE_NAME" desc="The name of the Internet Shortcut file created for URLs dragged that have no title">
           Untitled Webpage
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_SAFETY_CHECK_STRING_TUPLE_WITH_COMMA.png.sha1 b/ui/strings/ui_strings_grd/IDS_CONCAT_TWO_STRINGS_WITH_COMMA.png.sha1
similarity index 100%
rename from chrome/app/settings_strings_grdp/IDS_SETTINGS_SAFETY_CHECK_STRING_TUPLE_WITH_COMMA.png.sha1
rename to ui/strings/ui_strings_grd/IDS_CONCAT_TWO_STRINGS_WITH_COMMA.png.sha1
diff --git a/ui/strings/ui_strings_grd/IDS_CONCAT_TWO_STRINGS_WITH_PERIODS.png.sha1 b/ui/strings/ui_strings_grd/IDS_CONCAT_TWO_STRINGS_WITH_PERIODS.png.sha1
new file mode 100644
index 0000000..a774d948
--- /dev/null
+++ b/ui/strings/ui_strings_grd/IDS_CONCAT_TWO_STRINGS_WITH_PERIODS.png.sha1
@@ -0,0 +1 @@
+f39b3eea7e82b711a29c50f7cc12567bae2a80c3
\ No newline at end of file
diff --git a/ui/webui/resources/js/cr/ui/BUILD.gn b/ui/webui/resources/js/cr/ui/BUILD.gn
index d8fe3d7..fcabfbf 100644
--- a/ui/webui/resources/js/cr/ui/BUILD.gn
+++ b/ui/webui/resources/js/cr/ui/BUILD.gn
@@ -355,6 +355,7 @@
     ":focus_outline_manager",
     "..:ui",
     "../..:cr",
+    "../..:util",
   ]
 }
 
diff --git a/ui/webui/resources/js/plural_string_proxy.js b/ui/webui/resources/js/plural_string_proxy.js
index 5d38a70..801a414 100644
--- a/ui/webui/resources/js/plural_string_proxy.js
+++ b/ui/webui/resources/js/plural_string_proxy.js
@@ -20,6 +20,32 @@
    *     string for |messageName| with |itemCount| items.
    */
   getPluralString(messageName, itemCount) {}
+
+  /**
+   * Fetches both plural strings, concatenated to one string with a comma.
+   * @param {!string} messageName1 The name of the first message.
+   * @param {!number} itemCount1 The number of items in the first message.
+   * @param {!string} messageName2 The name of the second message.
+   * @param {!number} itemCount2 The number of items in the second message.
+   * @return {!Promise<string>} Promise resolved with the appropriate plural
+   *     strings for both messages, concatenated with a comma+whitespace in
+   *     between them.
+   */
+  getPluralStringTupleWithComma(
+      messageName1, itemCount1, messageName2, itemCount2) {}
+
+  /**
+   * Fetches both plural strings, concatenated to one string with periods.
+   * @param {!string} messageName1 The name of the first message.
+   * @param {!number} itemCount1 The number of items in the first message.
+   * @param {!string} messageName2 The name of the second message.
+   * @param {!number} itemCount2 The number of items in the second message.
+   * @return {!Promise<string>} Promise resolved with the appropriate plural
+   *     strings for both messages, concatenated with a period+whitespace after
+   *     the first message, and a period after the second message.
+   */
+  getPluralStringTupleWithPeriods(
+      messageName1, itemCount1, messageName2, itemCount2) {}
 }
 
 /** @implements {PluralStringProxy} */
@@ -28,6 +54,22 @@
   getPluralString(messageName, itemCount) {
     return sendWithPromise('getPluralString', messageName, itemCount);
   }
+
+  /** @override */
+  getPluralStringTupleWithComma(
+      messageName1, itemCount1, messageName2, itemCount2) {
+    return sendWithPromise(
+        'getPluralStringTupleWithComma', messageName1, itemCount1, messageName2,
+        itemCount2);
+  }
+
+  /** @override */
+  getPluralStringTupleWithPeriods(
+      messageName1, itemCount1, messageName2, itemCount2) {
+    return sendWithPromise(
+        'getPluralStringTupleWithPeriods', messageName1, itemCount1,
+        messageName2, itemCount2);
+  }
 }
 
 addSingletonGetter(PluralStringProxyImpl);