diff --git a/DEPS b/DEPS
index 00884e4..21df65c2 100644
--- a/DEPS
+++ b/DEPS
@@ -133,11 +133,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '7e603db010683f5123f267cefefabc33f33024d4',
+  'skia_revision': '3fd1841161d17c99a916da6fc0a2f8e5f06b962d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ed14aba38a9580529707e04a9b06a74f542e3f90',
+  'v8_revision': '5b1fde36ed3b4790d61938274cd83cfe88217ce4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -145,15 +145,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'e25ff9d82c06d1fba7c31a4a2c2678cc68e10fd6',
+  'angle_revision': '6f0c5b8dd5f2647131e345edea353419d5f35ed2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'dcc9fd7958e79ed3eb1fcc7e7f06a5545988f64e',
+  'swiftshader_revision': 'bb12b0e41a091fe6b76003e66f08196b88290a81',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'b33a01115c28d6ec31c3bbd5df0444f56a0ce824',
+  'pdfium_revision': '06d1acb7eb78a56699be59ca7f1d4fdf2bf64692',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'ed8036a40a27a1ec37e686b782a0066f69392935',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c467474e3a7a3d07908a2ecbc5bd81266ced9c13',
       'condition': 'checkout_linux',
   },
 
@@ -831,7 +831,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '5f6b911ad015b2660547353753bcfe50ae932fe2',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ccd2b4da9a7e6ddf126c0b4437db75201836154c',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1028,7 +1028,7 @@
   },
 
   'src/third_party/leveldatabase/src':
-    Var('chromium_git') + '/external/leveldb.git' + '@' + '3dc9202f78a3eb30ee8c0267e4e4be2e3f986e45',
+    Var('chromium_git') + '/external/leveldb.git' + '@' + '506b1722ef1a58d87325575d9bbcd3c8869381c7',
 
   'src/third_party/libFuzzer/src':
     Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git' + '@' +  Var('libfuzzer_revision'),
@@ -1184,7 +1184,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'caf3ef834ff80712738fc18c17c96468a8d89969',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '4d8300d86dec31f314d95271fd40ec210b0bf74a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1396,7 +1396,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1499f5591e06f8ed8e04af29f929d10cb4c4b83b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f3f53a5470410b6ca96542c03e04d185a984a099',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc
index af1fec2..c36336c 100644
--- a/ash/accelerators/accelerator_controller.cc
+++ b/ash/accelerators/accelerator_controller.cc
@@ -1902,7 +1902,8 @@
 
 bool AcceleratorController::ShouldSwapSideVolumeButtons(
     int source_device_id) const {
-  if (!Shell::Get()
+  if (!features::IsSwapSideVolumeButtonsForOrientationEnabled() ||
+      !Shell::Get()
            ->tablet_mode_controller()
            ->IsTabletModeWindowManagerEnabled() ||
       !IsSideVolumeButton(source_device_id)) {
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 14d710a2..9c4abf2 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -20,6 +20,7 @@
 #include "ash/magnifier/docked_magnifier_controller.h"
 #include "ash/magnifier/magnification_controller.h"
 #include "ash/media/media_controller.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/interfaces/ime_info.mojom.h"
@@ -1114,6 +1115,8 @@
     ws::InputDeviceClientTestApi().SetUncategorizedDevices({ui::InputDevice(
         kSideVolumeButtonId, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
         "cros_ec_buttons")});
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kSwapSideVolumeButtonsForOrientation);
   }
 
   bool IsLeftOrRightSide() const {
@@ -1127,6 +1130,7 @@
 
  private:
   std::string region_, side_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(SideVolumeButtonAcceleratorTest);
 };
diff --git a/ash/app_list/app_list_metrics.h b/ash/app_list/app_list_metrics.h
index cdb19d1..afdc671 100644
--- a/ash/app_list/app_list_metrics.h
+++ b/ash/app_list/app_list_metrics.h
@@ -245,7 +245,13 @@
   kEnterOverviewMode,
 
   // Exit the overview mode in tablet
-  kExitOverviewMode
+  kExitOverviewMode,
+
+  // Enter the kFullscreenAllApps state (usually by deactivating the search box)
+  kEnterFullscreenAllApps,
+
+  // Enter the kFullscreenSearch state (usually by activating the search box).
+  kEnterFullscreenSearch
 };
 
 void RecordFolderShowHideAnimationSmoothness(int actual_frames,
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index 0ff2632..56cd088 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -51,6 +51,7 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/test/test_windows.h"
 #include "ui/aura/window.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
@@ -249,6 +250,40 @@
   EXPECT_EQ(mojom::AppListViewState::kPeeking, app_list_view->app_list_state());
 }
 
+// Verifies that clicking on the search box in tablet mode with animation and
+// zero state enabled should not bring Chrome crash (https://crbug.com/958267).
+TEST_F(AppListPresenterDelegateZeroStateTest, ClickSearchBoxInTabletMode) {
+  // Necessary for AppListView::StateAnimationMetricsReporter::Report being
+  // called when animation ends.
+  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+  app_list::AppListView::SetShortAnimationForTesting(false);
+
+  EnableTabletMode(true);
+  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
+  GetAppListTestHelper()->CheckState(
+      ash::mojom::AppListViewState::kFullscreenAllApps);
+  ui::test::EventGenerator* generator = GetEventGenerator();
+
+  // Click on the search box.
+  generator->MoveMouseTo(GetPointInsideSearchbox());
+  generator->ClickLeftButton();
+
+  // Wait until animation finishes. Verifies AppListView's state.
+  base::RunLoop().RunUntilIdle();
+  GetAppListTestHelper()->CheckState(
+      ash::mojom::AppListViewState::kFullscreenSearch);
+
+  // Click on the area out of search box.
+  generator->MoveMouseTo(GetPointOutsideSearchbox());
+  generator->ClickLeftButton();
+
+  // Wait until animation finishes. Verifies AppListView's state.
+  base::RunLoop().RunUntilIdle();
+  GetAppListTestHelper()->CheckState(
+      ash::mojom::AppListViewState::kFullscreenAllApps);
+}
+
 TEST_F(PopulatedAppListTest, TappingAppsGridClosesVirtualKeyboard) {
   InitializeAppsGrid();
   app_list_test_model_->PopulateApps(2);
diff --git a/ash/app_list/presenter/app_list_presenter_impl.cc b/ash/app_list/presenter/app_list_presenter_impl.cc
index 4d1d5b6..7218127 100644
--- a/ash/app_list/presenter/app_list_presenter_impl.cc
+++ b/ash/app_list/presenter/app_list_presenter_impl.cc
@@ -332,6 +332,11 @@
   views::Widget* widget = view_->GetWidget();
   ui::Layer* layer = GetLayer(widget);
   layer->GetAnimator()->StopAnimating();
+
+  // Reset animation metrics reporter for AppListView when the animation is
+  // interrupted.
+  view_->ResetTransitionMetricsReporter();
+
   aura::Window* root_window = widget->GetNativeView()->GetRootWindow();
   const gfx::Vector2d offset =
       delegate_->GetVisibilityAnimationOffset(root_window);
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 109be11d..bd77dddd 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -294,6 +294,18 @@
           "Apps.HomeLauncherTransition.AnimationSmoothness.ExitOverview",
           value);
       break;
+    case TabletModeAnimationTransition::kEnterFullscreenAllApps:
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Apps.HomeLauncherTransition.AnimationSmoothness."
+          "EnterFullscreenAllApps",
+          value);
+      break;
+    case TabletModeAnimationTransition::kEnterFullscreenSearch:
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Apps.HomeLauncherTransition.AnimationSmoothness."
+          "EnterFullscreenSearch",
+          value);
+      break;
   }
 }
 
@@ -1485,14 +1497,31 @@
 
   ui::LayerAnimator* animator = layer->GetAnimator();
   animator->StopAnimating();
+
+  // Reset animation metrics reporter when animation is interrupted.
+  ResetTransitionMetricsReporter();
+
   ui::ScopedLayerAnimationSettings settings(animator);
   settings.SetTransitionDuration(
       base::TimeDelta::FromMilliseconds(animation_duration));
   settings.SetTweenType(gfx::Tween::EASE_OUT);
   settings.SetPreemptionStrategy(
       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
-  state_animation_metrics_reporter_->SetTargetState(target_state);
-  settings.SetAnimationMetricsReporter(state_animation_metrics_reporter_.get());
+
+  if (!is_tablet_mode_) {
+    state_animation_metrics_reporter_->SetTargetState(target_state);
+  } else {
+    DCHECK(target_state == ash::mojom::AppListViewState::kFullscreenAllApps ||
+           target_state == ash::mojom::AppListViewState::kFullscreenSearch);
+    TabletModeAnimationTransition transition_type =
+        target_state == ash::mojom::AppListViewState::kFullscreenAllApps
+            ? TabletModeAnimationTransition::kEnterFullscreenAllApps
+            : TabletModeAnimationTransition::kEnterFullscreenSearch;
+    state_animation_metrics_reporter_->SetTabletModeAnimationTransition(
+        transition_type);
+  }
+
+  settings.SetAnimationMetricsReporter(GetStateTransitionMetricsReporter());
   settings.AddObserver(transition_animation_observer_.get());
 
   layer->SetTransform(gfx::Transform());
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 155e8ad..79a39a8 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -78,6 +78,9 @@
 const base::Feature kSystemTrayFeaturePodsPagination{
     "SystemTrayFeaturePodsPagination", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kSwapSideVolumeButtonsForOrientation{
+    "SwapSideVolumeButtonsForOrientation", base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool IsHideArcMediaNotificationsEnabled() {
   return base::FeatureList::IsEnabled(kMediaSessionNotification) &&
          base::FeatureList::IsEnabled(kHideArcMediaNotifications);
@@ -147,5 +150,9 @@
   return base::FeatureList::IsEnabled(kSystemTrayFeaturePodsPagination);
 }
 
+bool IsSwapSideVolumeButtonsForOrientationEnabled() {
+  return base::FeatureList::IsEnabled(kSwapSideVolumeButtonsForOrientation);
+}
+
 }  // namespace features
 }  // namespace ash
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index b40a4412..295b433 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -95,6 +95,12 @@
 // Enables pagination for feature pod buttons in the system tray
 ASH_PUBLIC_EXPORT extern const base::Feature kSystemTrayFeaturePodsPagination;
 
+// Enables side volume button control based on screen orientation feature.
+// TODO(https://crbug.com/937907): Remove this after the feature is fully
+// launched.
+ASH_PUBLIC_EXPORT extern const base::Feature
+    kSwapSideVolumeButtonsForOrientation;
+
 ASH_PUBLIC_EXPORT bool IsHideArcMediaNotificationsEnabled();
 
 ASH_PUBLIC_EXPORT bool IsKeyboardShortcutViewerAppEnabled();
@@ -127,6 +133,8 @@
 
 ASH_PUBLIC_EXPORT bool IsSystemTrayFeaturePodsPaginationEnabled();
 
+ASH_PUBLIC_EXPORT bool IsSwapSideVolumeButtonsForOrientationEnabled();
+
 }  // namespace features
 }  // namespace ash
 
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 8ed3481f..5ae6f47d 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -208,10 +208,6 @@
   sequence_manager_->SetTimerSlack(timer_slack);
 }
 
-std::string MessageLoop::GetThreadName() const {
-  return sequence_manager_->GetThreadName();
-}
-
 scoped_refptr<SingleThreadTaskRunner> MessageLoop::task_runner() const {
   return sequence_manager_->GetTaskRunner();
 }
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index ddfed50..f3dde6e 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -152,13 +152,6 @@
   // Returns the type passed to the constructor.
   Type type() const { return type_; }
 
-  // Returns the name of the thread this message loop is bound to. This function
-  // is only valid when this message loop is running, BindToCurrentThread has
-  // already been called and has an "happens-before" relationship with this call
-  // (this relationship is obtained implicitly by the MessageLoop's task posting
-  // system unless calling this very early).
-  std::string GetThreadName() const;
-
   // Sets a new TaskRunner for this message loop. If the message loop was
   // already bound, this must be called on the thread to which it is bound.
   void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
diff --git a/base/message_loop/message_loop_current.cc b/base/message_loop/message_loop_current.cc
index 2e2adfa..71a45d0 100644
--- a/base/message_loop/message_loop_current.cc
+++ b/base/message_loop/message_loop_current.cc
@@ -51,10 +51,6 @@
   current_->RemoveDestructionObserver(destruction_observer);
 }
 
-std::string MessageLoopCurrent::GetThreadName() const {
-  return current_->GetThreadName();
-}
-
 scoped_refptr<SingleThreadTaskRunner> MessageLoopCurrent::task_runner() const {
   DCHECK(current_->IsBoundToCurrentThread());
   return current_->GetTaskRunner();
diff --git a/base/message_loop/message_loop_current.h b/base/message_loop/message_loop_current.h
index 270c6593..f259d89 100644
--- a/base/message_loop/message_loop_current.h
+++ b/base/message_loop/message_loop_current.h
@@ -105,9 +105,6 @@
   // DestructionObserver is receiving a notification callback.
   void RemoveDestructionObserver(DestructionObserver* destruction_observer);
 
-  // Returns the name for the thread associated with this object.
-  std::string GetThreadName() const;
-
   // Forwards to MessageLoop::task_runner().
   // DEPRECATED(https://crbug.com/616447): Use ThreadTaskRunnerHandle::Get()
   // instead of MessageLoopCurrent::Get()->task_runner().
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index 69ae6f0..9c6132c3 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -2238,22 +2238,6 @@
   EXPECT_EQ(loop.task_runner(), ThreadTaskRunnerHandle::Get());
 }
 
-TEST_F(MessageLoopTest, ThreadName) {
-  {
-    std::string kThreadName("foo");
-    MessageLoop loop;
-    PlatformThread::SetName(kThreadName);
-    EXPECT_EQ(kThreadName, loop.GetThreadName());
-  }
-
-  {
-    std::string kThreadName("bar");
-    base::Thread thread(kThreadName);
-    ASSERT_TRUE(thread.StartAndWaitForTesting());
-    EXPECT_EQ(kThreadName, thread.thread_name());
-  }
-}
-
 // Verify that tasks posted to and code running in the scope of the same
 // MessageLoop access the same SequenceLocalStorage values.
 TEST_F(MessageLoopTest, SequenceLocalStorageSetGet) {
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc
index 62d866f..20a6d97 100644
--- a/base/task/sequence_manager/sequence_manager_impl.cc
+++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -1019,14 +1019,6 @@
   return controller_->GetDefaultTaskRunner();
 }
 
-std::string SequenceManagerImpl::GetThreadName() const {
-  Optional<PlatformThreadId> thread_id = associated_thread_->GetBoundThreadId();
-  DCHECK(thread_id)
-      << "GetThreadName() must only be called after BindToCurrentThread()'s "
-      << "side-effects have been synchronized with this thread.";
-  return ThreadIdNameManager::GetInstance()->GetName(*thread_id);
-}
-
 bool SequenceManagerImpl::IsBoundToCurrentThread() const {
   return associated_thread_->IsBoundToCurrentThread();
 }
diff --git a/base/task/sequence_manager/sequence_manager_impl.h b/base/task/sequence_manager/sequence_manager_impl.h
index 14cc051..57c19e8 100644
--- a/base/task/sequence_manager/sequence_manager_impl.h
+++ b/base/task/sequence_manager/sequence_manager_impl.h
@@ -138,7 +138,6 @@
   void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
   // TODO(alexclarke): Remove this as part of https://crbug.com/825327.
   scoped_refptr<SingleThreadTaskRunner> GetTaskRunner();
-  std::string GetThreadName() const;
   bool IsBoundToCurrentThread() const;
   MessagePump* GetMessagePump() const;
   bool IsType(MessageLoop::Type type) const;
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index 4a8eb12..8f91346 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -4289,12 +4289,6 @@
   RunLoop().RunUntilIdle();
 }
 
-TEST_P(SequenceManagerTest, ThreadName) {
-  std::string kThreadName1("foo");
-  PlatformThread::SetName(kThreadName1);
-  EXPECT_EQ(kThreadName1, sequence_manager()->GetThreadName());
-}
-
 TEST_P(SequenceManagerTest, CreateTaskQueue) {
   scoped_refptr<TaskQueue> task_queue =
       sequence_manager()->CreateTaskQueue(TaskQueue::Spec("test"));
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index d24d1e3..67b540d 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8914618441507983984
\ No newline at end of file
+8914296474736728352
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index a24f789c..255f976 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8914652258186523104
\ No newline at end of file
+8914296671689431792
\ No newline at end of file
diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py
index 15e385e6..79b39e6 100755
--- a/build/vs_toolchain.py
+++ b/build/vs_toolchain.py
@@ -5,6 +5,7 @@
 
 from __future__ import print_function
 
+import collections
 import glob
 import json
 import os
@@ -22,9 +23,11 @@
 script_dir = os.path.dirname(os.path.realpath(__file__))
 json_data_file = os.path.join(script_dir, 'win_toolchain.json')
 
-
-# Use MSVS2017 as the default toolchain.
-CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2017'
+# VS versions are listed in descending order of priority (highest first).
+TOOLCHAIN_VERSIONS = collections.OrderedDict([
+  ('2017', '15.0'),
+  ('2019', '16.0'),
+])
 
 
 def SetEnvironmentAndGetRuntimeDllDirs():
@@ -129,9 +132,40 @@
 
 
 def GetVisualStudioVersion():
-  """Return GYP_MSVS_VERSION of Visual Studio.
+  """Return best available version of Visual Studio.
   """
-  return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION)
+
+  supported_versions = TOOLCHAIN_VERSIONS.keys()
+  supported_versions_str = ', '.join('{} ({})'.format(v,k)
+      for k,v in TOOLCHAIN_VERSIONS.items())
+  available_versions = []
+  for version in supported_versions:
+    for path in (
+        os.environ.get('vs%s_install' % version),
+        os.path.expandvars('%ProgramFiles(x86)%' +
+                           '/Microsoft Visual Studio/%s' % version)):
+      if path and os.path.exists(path):
+        available_versions.append(version)
+        break
+
+  if not available_versions:
+    if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))):
+      return TOOLCHAIN_VERSIONS.keys()[0]
+    raise Exception(('No supported Visual Studio can be found.'
+                     ' Supported versions are: %s.') % supported_versions_str)
+
+  env_version = os.environ.get('GYP_MSVS_VERSION')
+  if env_version:
+    if env_version not in supported_versions:
+      raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
+                       ' is not supported. Supported versions are: %s.')
+                       % (env_version, supported_versions_str))
+    if env_version not in available_versions:
+      raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
+                       ' is not available.') % env_version)
+    return env_version
+
+  return available_versions[0]
 
 
 def DetectVisualStudioPath():
@@ -141,14 +175,6 @@
   # Note that this code is used from
   # build/toolchain/win/setup_toolchain.py as well.
   version_as_year = GetVisualStudioVersion()
-  year_to_version = {
-      '2017': '15.0',
-      '2019': '16.0',
-  }
-  if version_as_year not in year_to_version:
-    raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
-                     ' not supported. Supported versions are: %s') % (
-                       version_as_year, ', '.join(year_to_version.keys())))
 
   # The VC++ >=2017 install location needs to be located using COM instead of
   # the registry. For details see:
@@ -267,7 +293,7 @@
   version number part changes frequently so the highest version number found is
   used.
   """
-  assert GetVisualStudioVersion() in ['2017', '2019']
+
   SetEnvironmentAndGetRuntimeDllDirs()
   assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ)
   vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index 0a9ea808..114ee63 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -10,6 +10,7 @@
 #include <algorithm>
 #include <vector>
 
+#include "base/memory/shared_memory_mapping.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
@@ -156,12 +157,12 @@
       const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid,
       uint64_t tracing_process_id,
       int importance) const override {
-    pmd->CreateSharedMemoryOwnershipEdge(
-        buffer_dump_guid, shared_memory->mapped_id(), importance);
+    pmd->CreateSharedMemoryOwnershipEdge(buffer_dump_guid,
+                                         shared_mapping.guid(), importance);
   }
 
   LayerTreeFrameSink* layer_tree_frame_sink;
-  std::unique_ptr<base::SharedMemory> shared_memory;
+  base::WritableSharedMemoryMapping shared_mapping;
 };
 
 bool HeadsUpDisplayLayerImpl::WillDraw(
@@ -320,15 +321,14 @@
       auto backing = std::make_unique<HudSoftwareBacking>();
       backing->layer_tree_frame_sink = layer_tree_frame_sink;
       backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-      backing->shared_memory = viz::bitmap_allocation::AllocateMappedBitmap(
-          pool_resource.size(), pool_resource.format());
+      base::MappedReadOnlyRegion mapped_region =
+          viz::bitmap_allocation::AllocateSharedBitmap(pool_resource.size(),
+                                                       pool_resource.format());
+      backing->shared_mapping = std::move(mapped_region.mapping);
 
-      mojo::ScopedSharedBufferHandle handle =
-          viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(
-              backing->shared_memory.get(), pool_resource.size(),
-              pool_resource.format());
-      layer_tree_frame_sink->DidAllocateSharedBitmap(std::move(handle),
-                                                     backing->shared_bitmap_id);
+      layer_tree_frame_sink->DidAllocateSharedBitmap(
+          viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
+          backing->shared_bitmap_id);
 
       pool_resource.set_software_backing(std::move(backing));
     }
@@ -443,7 +443,7 @@
     auto* backing =
         static_cast<HudSoftwareBacking*>(pool_resource.software_backing());
     sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(
-        info, backing->shared_memory->memory(), info.minRowBytes());
+        info, backing->shared_mapping.memory(), info.minRowBytes());
 
     SkiaPaintCanvas canvas(surface->getCanvas());
     DrawHudContents(&canvas);
diff --git a/cc/layers/texture_layer_impl.cc b/cc/layers/texture_layer_impl.cc
index 9d2e075..6944908 100644
--- a/cc/layers/texture_layer_impl.cc
+++ b/cc/layers/texture_layer_impl.cc
@@ -110,15 +110,9 @@
 
   LayerTreeFrameSink* sink = layer_tree_impl()->layer_tree_frame_sink();
   for (const auto& pair : to_register_bitmaps_) {
-    // Because we may want to notify a display compositor about this
-    // base::SharedMemory more than one time, we need to be able to keep
-    // making handles to share with it, so we can't close the
-    // base::SharedMemory.
-    mojo::ScopedSharedBufferHandle handle =
-        viz::bitmap_allocation::DuplicateWithoutClosingMappedBitmap(
-            pair.second->shared_memory(), pair.second->size(),
-            pair.second->format());
-    sink->DidAllocateSharedBitmap(std::move(handle), pair.first);
+    sink->DidAllocateSharedBitmap(viz::bitmap_allocation::ToMojoHandle(
+                                      pair.second->shared_region().Duplicate()),
+                                  pair.first);
   }
   // All |to_register_bitmaps_| have been registered above, so we can move them
   // all to the |registered_bitmaps_|.
diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc
index 33bdd003..7aec79fdf 100644
--- a/cc/layers/texture_layer_unittest.cc
+++ b/cc/layers/texture_layer_unittest.cc
@@ -1533,7 +1533,7 @@
 
     id_ = viz::SharedBitmap::GenerateId();
     bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
@@ -1637,7 +1637,7 @@
 
     id_ = viz::SharedBitmap::GenerateId();
     bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
@@ -1717,11 +1717,11 @@
 
     id1_ = viz::SharedBitmap::GenerateId();
     bitmap1_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id1_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id1_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
     id2_ = viz::SharedBitmap::GenerateId();
     bitmap2_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id2_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id2_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
@@ -1811,11 +1811,11 @@
 
     id1_ = viz::SharedBitmap::GenerateId();
     bitmap1_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id1_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id1_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
     id2_ = viz::SharedBitmap::GenerateId();
     bitmap2_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id2_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id2_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
@@ -1900,7 +1900,7 @@
 
     id_ = viz::SharedBitmap::GenerateId();
     bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
@@ -2015,7 +2015,7 @@
 
     id_ = viz::SharedBitmap::GenerateId();
     bitmap_ = base::MakeRefCounted<CrossThreadSharedBitmap>(
-        id_, viz::bitmap_allocation::AllocateMappedBitmap(size, format), size,
+        id_, viz::bitmap_allocation::AllocateSharedBitmap(size, format), size,
         format);
   }
 
diff --git a/cc/raster/bitmap_raster_buffer_provider.cc b/cc/raster/bitmap_raster_buffer_provider.cc
index f94e220c..408fb0f 100644
--- a/cc/raster/bitmap_raster_buffer_provider.cc
+++ b/cc/raster/bitmap_raster_buffer_provider.cc
@@ -9,6 +9,7 @@
 
 #include <algorithm>
 
+#include "base/memory/shared_memory_mapping.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/process_memory_dump.h"
 #include "base/trace_event/trace_event.h"
@@ -32,12 +33,12 @@
       const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid,
       uint64_t tracing_process_id,
       int importance) const override {
-    pmd->CreateSharedMemoryOwnershipEdge(
-        buffer_dump_guid, shared_memory->mapped_id(), importance);
+    pmd->CreateSharedMemoryOwnershipEdge(buffer_dump_guid, mapping.guid(),
+                                         importance);
   }
 
   LayerTreeFrameSink* frame_sink;
-  std::unique_ptr<base::SharedMemory> shared_memory;
+  base::WritableSharedMemoryMapping mapping;
 };
 
 class BitmapRasterBufferImpl : public RasterBuffer {
@@ -107,14 +108,12 @@
     auto backing = std::make_unique<BitmapSoftwareBacking>();
     backing->frame_sink = frame_sink_;
     backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-    backing->shared_memory =
-        viz::bitmap_allocation::AllocateMappedBitmap(size, viz::RGBA_8888);
-
-    mojo::ScopedSharedBufferHandle handle =
-        viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(
-            backing->shared_memory.get(), size, viz::RGBA_8888);
-    frame_sink_->DidAllocateSharedBitmap(std::move(handle),
-                                         backing->shared_bitmap_id);
+    base::MappedReadOnlyRegion mapped_region =
+        viz::bitmap_allocation::AllocateSharedBitmap(size, viz::RGBA_8888);
+    backing->mapping = std::move(mapped_region.mapping);
+    frame_sink_->DidAllocateSharedBitmap(
+        viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
+        backing->shared_bitmap_id);
 
     resource.set_software_backing(std::move(backing));
   }
@@ -122,7 +121,7 @@
       static_cast<BitmapSoftwareBacking*>(resource.software_backing());
 
   return std::make_unique<BitmapRasterBufferImpl>(
-      size, color_space, backing->shared_memory->memory(), resource_content_id,
+      size, color_space, backing->mapping.memory(), resource_content_id,
       previous_content_id);
 }
 
diff --git a/cc/resources/cross_thread_shared_bitmap.cc b/cc/resources/cross_thread_shared_bitmap.cc
index 642bd39..edd1e523 100644
--- a/cc/resources/cross_thread_shared_bitmap.cc
+++ b/cc/resources/cross_thread_shared_bitmap.cc
@@ -6,12 +6,15 @@
 
 namespace cc {
 
-CrossThreadSharedBitmap::CrossThreadSharedBitmap(
-    const viz::SharedBitmapId& id,
-    std::unique_ptr<base::SharedMemory> memory,
-    const gfx::Size& size,
-    viz::ResourceFormat format)
-    : id_(id), memory_(std::move(memory)), size_(size), format_(format) {}
+CrossThreadSharedBitmap::CrossThreadSharedBitmap(const viz::SharedBitmapId& id,
+                                                 base::MappedReadOnlyRegion shm,
+                                                 const gfx::Size& size,
+                                                 viz::ResourceFormat format)
+    : id_(id),
+      region_(std::move(shm.region)),
+      mapping_(std::move(shm.mapping)),
+      size_(size),
+      format_(format) {}
 
 CrossThreadSharedBitmap::~CrossThreadSharedBitmap() = default;
 
diff --git a/cc/resources/cross_thread_shared_bitmap.h b/cc/resources/cross_thread_shared_bitmap.h
index f594ef1..6d7017a6a 100644
--- a/cc/resources/cross_thread_shared_bitmap.h
+++ b/cc/resources/cross_thread_shared_bitmap.h
@@ -7,8 +7,9 @@
 
 #include <memory>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "cc/cc_export.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/resources/shared_bitmap.h"
@@ -16,22 +17,25 @@
 
 namespace cc {
 
-// This class holds ownership of a base::SharedMemory segment for use as a
-// composited resource, and is refcounted in order to share ownership with the
-// LayerTreeHost, via TextureLayer, which needs access to the base::SharedMemory
-// from the compositor thread.
-// Because all the fields exposed are const, they can be used from any thread
-// without conflict, as they only read existing states.
+// This class holds ownership of a base::ReadOnlySharedMemoryRegion and its
+// base::WritableSharedMemoryMapping for use as a composited resource, and is
+// refcounted in order to share ownership with the LayerTreeHost, via
+// TextureLayer, which needs access to the base::ReadOnlySharedMemory from
+// the compositor thread. Because all the fields exposed are const, they can
+// be used from any thread without conflict, as they only read existing states.
 class CC_EXPORT CrossThreadSharedBitmap
     : public base::RefCountedThreadSafe<CrossThreadSharedBitmap> {
  public:
   CrossThreadSharedBitmap(const viz::SharedBitmapId& id,
-                          std::unique_ptr<base::SharedMemory> memory,
+                          base::MappedReadOnlyRegion shm,
                           const gfx::Size& size,
                           viz::ResourceFormat format);
 
   const viz::SharedBitmapId& id() const { return id_; }
-  const base::SharedMemory* shared_memory() const { return memory_.get(); }
+  const base::ReadOnlySharedMemoryRegion& shared_region() const {
+    return region_;
+  }
+  void* memory() const { return mapping_.memory(); }
   const gfx::Size& size() const { return size_; }
   viz::ResourceFormat format() const { return format_; }
 
@@ -41,7 +45,8 @@
   ~CrossThreadSharedBitmap();
 
   const viz::SharedBitmapId id_;
-  const std::unique_ptr<const base::SharedMemory> memory_;
+  const base::ReadOnlySharedMemoryRegion region_;
+  base::WritableSharedMemoryMapping mapping_;
   const gfx::Size size_;
   const viz::ResourceFormat format_;
 };
diff --git a/cc/test/pixel_test.cc b/cc/test/pixel_test.cc
index e35f5b85..e85a700 100644
--- a/cc/test/pixel_test.cc
+++ b/cc/test/pixel_test.cc
@@ -7,7 +7,8 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
@@ -210,27 +211,26 @@
       *result_bitmap_, test_data_dir.Append(ref_file), comparator);
 }
 
-std::unique_ptr<base::SharedMemory> PixelTest::AllocateSharedBitmapMemory(
+base::WritableSharedMemoryMapping PixelTest::AllocateSharedBitmapMemory(
     const viz::SharedBitmapId& id,
     const gfx::Size& size) {
-  std::unique_ptr<base::SharedMemory> shm =
-      viz::bitmap_allocation::AllocateMappedBitmap(size, viz::RGBA_8888);
+  base::MappedReadOnlyRegion mapped_region =
+      viz::bitmap_allocation::AllocateSharedBitmap(size, viz::RGBA_8888);
   this->shared_bitmap_manager_->ChildAllocatedSharedBitmap(
-      viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size,
-                                                            viz::RGBA_8888),
+      viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
       id);
-  return shm;
+  return std::move(mapped_region.mapping);
 }
 
 viz::ResourceId PixelTest::AllocateAndFillSoftwareResource(
     const gfx::Size& size,
     const SkBitmap& source) {
   viz::SharedBitmapId shared_bitmap_id = viz::SharedBitmap::GenerateId();
-  std::unique_ptr<base::SharedMemory> shm =
+  base::WritableSharedMemoryMapping mapping =
       AllocateSharedBitmapMemory(shared_bitmap_id, size);
 
   SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
-  source.readPixels(info, shm->memory(), info.minRowBytes(), 0, 0);
+  source.readPixels(info, mapping.memory(), info.minRowBytes(), 0, 0);
 
   return child_resource_provider_->ImportResource(
       viz::TransferableResource::MakeSoftware(shared_bitmap_id, size,
diff --git a/cc/test/pixel_test.h b/cc/test/pixel_test.h
index 3fdb4f20..d9529c3 100644
--- a/cc/test/pixel_test.h
+++ b/cc/test/pixel_test.h
@@ -6,6 +6,7 @@
 #define CC_TEST_PIXEL_TEST_H_
 
 #include "base/files/file_util.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/single_thread_task_runner.h"
 #include "cc/test/pixel_comparator.h"
 #include "cc/trees/layer_tree_settings.h"
@@ -79,7 +80,7 @@
 
   // Allocates a SharedMemory bitmap and registers it with the display
   // compositor's SharedBitmapManager.
-  std::unique_ptr<base::SharedMemory> AllocateSharedBitmapMemory(
+  base::WritableSharedMemoryMapping AllocateSharedBitmapMemory(
       const viz::SharedBitmapId& id,
       const gfx::Size& size);
   // Uses AllocateSharedBitmapMemory() then registers a ResourceId with the
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index e09d1b99..eea90a16 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -21,6 +21,7 @@
 #include "base/containers/flat_map.h"
 #include "base/json/json_writer.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/metrics/histogram.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/stl_util.h"
@@ -5326,7 +5327,7 @@
   GLenum texture_target = GL_TEXTURE_2D;
   // For software compositing, shared memory will be allocated and the
   // UIResource will be copied into it.
-  std::unique_ptr<base::SharedMemory> shared_memory;
+  base::MappedReadOnlyRegion mapped_region;
   viz::SharedBitmapId shared_bitmap_id;
   bool overlay_candidate = false;
 
@@ -5344,8 +5345,8 @@
                                                    BufferFormat(format), caps);
     }
   } else {
-    shared_memory =
-        viz::bitmap_allocation::AllocateMappedBitmap(upload_size, format);
+    mapped_region =
+        viz::bitmap_allocation::AllocateSharedBitmap(upload_size, format);
     shared_bitmap_id = viz::SharedBitmap::GenerateId();
   }
 
@@ -5370,7 +5371,7 @@
           SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(upload_size));
 
       sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(
-          dst_info, shared_memory->memory(), dst_info.minRowBytes());
+          dst_info, mapped_region.mapping.memory(), dst_info.minRowBytes());
       surface->getCanvas()->writePixels(
           src_info, const_cast<uint8_t*>(bitmap.GetPixels()),
           src_info.minRowBytes(), 0, 0);
@@ -5406,7 +5407,7 @@
       SkImageInfo dst_info =
           SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(upload_size));
       scaled_surface = SkSurface::MakeRasterDirect(
-          dst_info, shared_memory->memory(), dst_info.minRowBytes());
+          dst_info, mapped_region.mapping.memory(), dst_info.minRowBytes());
     }
     SkCanvas* scaled_canvas = scaled_surface->getCanvas();
     scaled_canvas->scale(canvas_scale_x, canvas_scale_y);
@@ -5449,11 +5450,9 @@
         overlay_candidate);
     transferable.format = format;
   } else {
-    mojo::ScopedSharedBufferHandle memory_handle =
-        viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(
-            shared_memory.get(), upload_size, format);
-    layer_tree_frame_sink_->DidAllocateSharedBitmap(std::move(memory_handle),
-                                                    shared_bitmap_id);
+    layer_tree_frame_sink_->DidAllocateSharedBitmap(
+        viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
+        shared_bitmap_id);
     transferable = viz::TransferableResource::MakeSoftware(shared_bitmap_id,
                                                            upload_size, format);
   }
@@ -5471,7 +5470,7 @@
   data.opaque = bitmap.GetOpaque();
   data.format = format;
   data.shared_bitmap_id = shared_bitmap_id;
-  data.shared_memory = std::move(shared_memory);
+  data.shared_mapping = std::move(mapped_region.mapping);
   data.mailbox = mailbox;
   data.resource_id_for_export = id;
   ui_resource_map_[uid] = std::move(data);
@@ -5501,8 +5500,8 @@
     UIResourceData data,
     const gpu::SyncToken& sync_token) {
   // Resources are either software or gpu backed, not both.
-  DCHECK(!(data.shared_memory && !data.mailbox.IsZero()));
-  if (data.shared_memory)
+  DCHECK(!(data.shared_mapping.IsValid() && !data.mailbox.IsZero()));
+  if (data.shared_mapping.IsValid())
     layer_tree_frame_sink_->DidDeleteSharedBitmap(data.shared_bitmap_id);
   if (!data.mailbox.IsZero()) {
     auto* sii =
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 18ccfb6..cd4dc10 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -18,6 +18,7 @@
 #include "base/containers/circular_deque.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/memory_pressure_listener.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "cc/base/synced_property.h"
@@ -219,7 +220,7 @@
 
     // Backing for software compositing.
     viz::SharedBitmapId shared_bitmap_id;
-    std::unique_ptr<base::SharedMemory> shared_memory;
+    base::WritableSharedMemoryMapping shared_mapping;
     // Backing for gpu compositing.
     gpu::Mailbox mailbox;
 
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index ef1dd234..358d9e1 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -504,6 +504,9 @@
         "//third_party/blink/public:blink",
         "//gin",
         "//v8",
+
+        # Ditto for PDFium.
+        "//third_party/pdfium",
       ]
     } else {
       deps += [
@@ -1511,6 +1514,9 @@
         # V8/Gin should not be used in the browser DLL on Windows.
         "//gin",
         "//v8",
+
+        # Ditto for PDFium.
+        "//third_party/pdfium",
       ]
     }
   }
diff --git a/chrome/VERSION b/chrome/VERSION
index ec07c77a..8bfed3d 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=76
 MINOR=0
-BUILD=3785
+BUILD=3786
 PATCH=0
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
index 48ce147..f8dc51f 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
@@ -15,10 +15,14 @@
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
+import java.util.Collections;
+
 /**
  * Coordinator for the header of the Autofill Assistant.
  */
 public class AssistantHeaderCoordinator {
+    private ProfileDataCache mProfileCache;
+
     public AssistantHeaderCoordinator(
             Context context, ViewGroup bottomBarView, AssistantHeaderModel model) {
         // Create the poodle and insert it before the status message. We have to create a view
@@ -31,7 +35,8 @@
                         R.dimen.autofill_assistant_poodle_size));
         addPoodle(bottomBarView, poodle.getView());
 
-        setProfileImage(bottomBarView, context);
+        mProfileCache = new ProfileDataCache(context, R.dimen.autofill_assistant_profile_size);
+        setupProfileImage(bottomBarView);
 
         // Bind view and mediator through the model.
         AssistantHeaderViewBinder.ViewHolder viewHolder =
@@ -50,15 +55,19 @@
     }
 
     // TODO(b/130415092): Use image from AGSA if chrome is not signed in.
-    private void setProfileImage(ViewGroup root, Context context) {
+    private void setupProfileImage(ViewGroup root) {
         String signedInAccountName = ChromeSigninController.get().getSignedInAccountName();
         if (signedInAccountName != null) {
-            ProfileDataCache profileCache =
-                    new ProfileDataCache(context, R.dimen.autofill_assistant_profile_size);
-            DisplayableProfileData profileData =
-                    profileCache.getProfileDataOrDefault(signedInAccountName);
-            ImageView profileView = root.findViewById(R.id.profile_image);
-            profileView.setImageDrawable(profileData.getImage());
+            mProfileCache.addObserver(account -> {
+                if (!signedInAccountName.equals(account)) {
+                    return;
+                }
+                DisplayableProfileData profileData =
+                        mProfileCache.getProfileDataOrDefault(signedInAccountName);
+                ImageView profileView = root.findViewById(R.id.profile_image);
+                profileView.setImageDrawable(profileData.getImage());
+            });
+            mProfileCache.update(Collections.singletonList(signedInAccountName));
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 8f3e186..c858161 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -201,6 +201,8 @@
     public static final String CONTEXTUAL_SEARCH_DEFINITIONS = "ContextualSearchDefinitions";
     public static final String CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION =
             "ContextualSearchMlTapSuppression";
+    public static final String CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE =
+            "ContextualSearchLongpressResolve";
     public static final String CONTEXTUAL_SEARCH_SIMPLIFIED_SERVER =
             "ContextualSearchSimplifiedServer";
     public static final String CONTEXTUAL_SEARCH_SECOND_TAP = "ContextualSearchSecondTap";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelAnimation.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelAnimation.java
index 63fa8b6..962f2566 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelAnimation.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelAnimation.java
@@ -164,6 +164,22 @@
         }
     }
 
+    @Override
+    public void hidePanel(@StateChangeReason int reason) {
+        if (getPanelState() == PanelState.PEEKED) {
+            mPanelHidden = true;
+            animatePanelToState(PanelState.CLOSED, reason);
+        }
+    }
+
+    @Override
+    public void showPanel(@StateChangeReason int reason) {
+        if (mPanelHidden) {
+            animatePanelToState(PanelState.PEEKED, reason);
+            mPanelHidden = false;
+        }
+    }
+
     /**
      * Updates the Panel so it preserves its state when the size changes.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
index 656a41a..6ab4b68 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
@@ -162,6 +162,9 @@
         return mContext;
     }
 
+    /** Tracks whether the panel has been hidden. {@See #showPanel, #hidePanel}.  */
+    protected boolean mPanelHidden;
+
     /**
      * Animates the Overlay Panel to its closed state.
      * @param reason The reason for the change of panel state.
@@ -175,6 +178,12 @@
      */
     protected abstract void onClosed(@StateChangeReason int reason);
 
+    /** Temporarily hides a peeking panel for the given reason.  Does nothing if not peeking. */
+    public abstract void hidePanel(@StateChangeReason int reason);
+
+    /** Shows a previously hidden panel again.  {@See #hidePanel}. */
+    public abstract void showPanel(@StateChangeReason int reason);
+
     /**
      * TODO(mdjones): This method should be removed from this class.
      * @return The resource id that contains how large the browser controls are.
@@ -587,6 +596,8 @@
      * @param reason The reason for a change in the panel's state.
      */
     protected void setPanelState(@PanelState int state, @StateChangeReason int reason) {
+        if (mPanelHidden) return;
+
         if (state == PanelState.CLOSED) {
             mHeight = 0;
             onClosed(reason);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
index 969f600..807da0f3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
@@ -13,6 +13,7 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.LayerTitleCache;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent;
@@ -22,6 +23,7 @@
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
 import org.chromium.chrome.browser.compositor.scene_layer.ContextualSearchSceneLayer;
 import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate;
 import org.chromium.chrome.browser.contextualsearch.ResolvedSearchTerm.CardTag;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -35,11 +37,8 @@
  * Controls the Contextual Search Panel.
  */
 public class ContextualSearchPanel extends OverlayPanel {
-    /** When using the Generic UX we never show the Arrow Icon */
-    private static final float ARROW_ICON_OPACITY_GENERIC_UX = 0.f;
-
-    /** When using the Generic UX we always show the Close Icon */
-    private static final float CLOSE_ICON_OPACITY_GENERIC_UX = 1.f;
+    /** The number of times to allow scrolling.  After this limit we'll close. */
+    private static final int SCROLL_COUNT_LIMIT = 3;
 
     /** Used for logging state changes. */
     private final ContextualSearchPanelMetrics mPanelMetrics;
@@ -74,6 +73,9 @@
      */
     private ScrimParams mScrimParams;
 
+    /** Number of times the panel has been scrolled already. */
+    private int mScrollCount;
+
     // ============================================================================================
     // Constructor
     // ============================================================================================
@@ -238,6 +240,7 @@
         setProgressBarCompletion(0);
         setProgressBarVisible(false);
         getImageControl().hideCustomImage(false);
+        mScrollCount = 0;
 
         super.onClosed(reason);
 
@@ -445,6 +448,28 @@
     }
 
     /**
+     * Makes the panel not visible by either hiding it or closing it completely.
+     * Decides which method is most appropriate, and then makes it not visible based on that
+     * decision.
+     * @param reason The reason we want the panel to not be visible.
+     */
+    public void makePanelNotVisible(@StateChangeReason int reason) {
+        if (++mScrollCount >= SCROLL_COUNT_LIMIT) {
+            closePanel(StateChangeReason.BASE_PAGE_SCROLL, true);
+        } else if (isHideDuringScrollEnabled()) {
+            hidePanel(reason);
+        }
+    }
+
+    /** @return whether hiding during scrolling is enabled for the Longpress-Resolve feature. */
+    private boolean isHideDuringScrollEnabled() {
+        return ContextualSearchFieldTrial.LONGPRESS_RESOLVE_HIDE_ON_SCROLL.equals(
+                ChromeFeatureList.getFieldTrialParamByFeature(
+                        ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE,
+                        ContextualSearchFieldTrial.LONGPRESS_RESOLVE_PARAM_NAME));
+    }
+
+    /**
      * Called after the panel has navigated to prefetched Search Results.
      * If the user has the panel open then they will see the prefetched result starting to load.
      * Currently this just logs the time between the start of the search until the results start to
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
index 72ab62b6..484a7bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
@@ -185,7 +185,10 @@
             analyzeTap(startOffset);
         }
         // Notify of an initial selection if it's not empty.
-        if (endOffset > startOffset) onSelectionChanged();
+        if (endOffset > startOffset) {
+            updateInitialSelectedWord();
+            onSelectionChanged();
+        }
         if (setNative) {
             nativeSetContent(getNativePointer(), mSurroundingText, mSelectionStartOffset,
                     mSelectionEndOffset);
@@ -273,6 +276,16 @@
     }
 
     /**
+     * Specifies that this resolve must return a non-expanding result.
+     */
+    void setRestrictedResolve() {
+        mSurroundingText = mInitialSelectedWord;
+        mSelectionStartOffset = 0;
+        mSelectionEndOffset = mSurroundingText.length();
+        nativeRestrictResolve(mNativePointer);
+    }
+
+    /**
      * Notifies of an adjustment that has been applied to the start and end of the selection.
      * @param startAdjust A signed value indicating the direction of the adjustment to the start of
      *        the selection (typically a negative value when the selection expands).
@@ -283,6 +296,14 @@
         // Fully track the selection as it changes.
         mSelectionStartOffset += startAdjust;
         mSelectionEndOffset += endAdjust;
+        updateInitialSelectedWord();
+        nativeAdjustSelection(getNativePointer(), startAdjust, endAdjust);
+        // Notify of changes.
+        onSelectionChanged();
+    }
+
+    /** Updates the initial selected word if it has not yet been set. */
+    private void updateInitialSelectedWord() {
         if (TextUtils.isEmpty(mInitialSelectedWord) && !TextUtils.isEmpty(mSurroundingText)) {
             // TODO(donnd): investigate the root cause of crbug.com/725027 that requires this
             // additional validation to prevent this substring call from crashing!
@@ -290,13 +311,9 @@
                     || mSelectionEndOffset > mSurroundingText.length()) {
                 return;
             }
-
             mInitialSelectedWord =
                     mSurroundingText.substring(mSelectionStartOffset, mSelectionEndOffset);
         }
-        nativeAdjustSelection(getNativePointer(), startAdjust, endAdjust);
-        // Notify of changes.
-        onSelectionChanged();
     }
 
     /** @return the current selection, or an empty string if data is invalid or nothing selected. */
@@ -568,4 +585,6 @@
             int selectionStart, int selectionEnd);
     @VisibleForTesting
     protected native String nativeDetectLanguage(long nativeContextualSearchContext);
+    @VisibleForTesting
+    protected native void nativeRestrictResolve(long nativeContextualSearchContext);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
index 66343b1..d1c3b35a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
@@ -21,6 +21,14 @@
  * Provides Field Trial support for the Contextual Search application within Chrome for Android.
  */
 public class ContextualSearchFieldTrial {
+    //==========================================================================================
+    // Public settings synchronized with src/components/contextual_search/core/browser/public.cc
+    //==========================================================================================
+    public static final String LONGPRESS_RESOLVE_PARAM_NAME = "longpress_resolve_variation";
+    public static final String LONGPRESS_RESOLVE_HIDE_ON_SCROLL = "1";
+    public static final String LONGPRESS_RESOLVE_PRIVACY_AGGRESSIVE = "2";
+
+    //==========================================================================================
     private static final String FIELD_TRIAL_NAME = "ContextualSearch";
     private static final String DISABLED_PARAM = "disabled";
     private static final String ENABLED_VALUE = "true";
@@ -32,6 +40,7 @@
     private static Boolean[] sSwitches = new Boolean[ContextualSearchSwitch.NUM_ENTRIES];
     private static Integer[] sSettings = new Integer[ContextualSearchSetting.NUM_ENTRIES];
 
+    // SWITCHES
     // TODO(donnd): remove all supporting code once short-lived data collection is done.
     @IntDef({ContextualSearchSwitch.IS_TRANSLATION_DISABLED,
             ContextualSearchSwitch.IS_ONLINE_DETECTION_DISABLED,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
index ed94323..d22dfc4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateController.java
@@ -10,6 +10,7 @@
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchSelectionController.SelectionType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -44,6 +45,9 @@
     private final ContextualSearchPolicy mPolicy;
     private final ContextualSearchInternalStateHandler mStateHandler;
 
+    // The type of selection that triggered this state-change sequence;
+    private @SelectionType int mSelectionType;
+
     /**
      * The current internal state of the {@code ContextualSearchManager}.
      * States can be "start states" which can be passed to #enter(), or "transitional states" which
@@ -51,14 +55,25 @@
      * "resting states" which do not transition into any next state, or a combination of the
      * above.
      */
-    @IntDef({InternalState.UNDEFINED, InternalState.IDLE, InternalState.LONG_PRESS_RECOGNIZED,
-            InternalState.SHOWING_LONGPRESS_SEARCH, InternalState.SELECTION_CLEARED_RECOGNIZED,
-            InternalState.WAITING_FOR_POSSIBLE_TAP_NEAR_PREVIOUS, InternalState.TAP_RECOGNIZED,
+    @IntDef({
+            InternalState.UNDEFINED,
+            InternalState.IDLE,
+            InternalState.LONG_PRESS_RECOGNIZED,
+            InternalState.SHOWING_LONGPRESS_SEARCH,
+            InternalState.SELECTION_CLEARED_RECOGNIZED,
+            InternalState.WAITING_FOR_POSSIBLE_TAP_NEAR_PREVIOUS,
+            InternalState.TAP_RECOGNIZED,
             InternalState.WAITING_FOR_POSSIBLE_TAP_ON_TAP_SELECTION,
-            InternalState.TAP_GESTURE_COMMIT, InternalState.GATHERING_SURROUNDINGS,
-            InternalState.DECIDING_SUPPRESSION, InternalState.START_SHOWING_TAP_UI,
-            InternalState.SHOW_FULL_TAP_UI, InternalState.RESOLVING,
-            InternalState.SHOWING_TAP_SEARCH})
+            InternalState.TAP_GESTURE_COMMIT,
+            InternalState.GATHERING_SURROUNDINGS,
+            InternalState.DECIDING_SUPPRESSION,
+            InternalState.START_SHOWING_TAP_UI,
+            InternalState.SHOW_RESOLVING_UI,
+            InternalState.RESOLVING,
+            InternalState.SHOWING_TAP_SEARCH,
+            InternalState.RESOLVING_LONG_PRESS_RECOGNIZED,
+            InternalState.SHOWING_RESOLVED_LONG_PRESS_SEARCH,
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InternalState {
         /**
@@ -121,7 +136,7 @@
         /**
          * Show the full Tap UI. Currently this means showing the Overlay Panel.
          */
-        int SHOW_FULL_TAP_UI = 12;
+        int SHOW_RESOLVING_UI = 12;
         /**
          * Resolving the Search Term using the surrounding text and additional context.
          * Currently this makes a server request, which could take a long time.
@@ -131,6 +146,15 @@
          * Resting state when showing the panel in response to a Tap gesture.
          */
         int SHOWING_TAP_SEARCH = 14;
+        /**
+         * This starts resolving transition that leads to the SHOWING_LONGPRESS_SEARCH resting
+         * state.
+         */
+        int RESOLVING_LONG_PRESS_RECOGNIZED = 14;
+        /**
+         * Resting state when showing the panel in response to a longpress gesture that resolved.
+         */
+        int SHOWING_RESOLVED_LONG_PRESS_SEARCH = 15;
     }
 
     // The current state of this instance.
@@ -175,6 +199,7 @@
      * @param reason The reason for the reset.
      */
     void reset(@Nullable @StateChangeReason Integer reason) {
+        mSelectionType = SelectionType.UNDETERMINED;
         transitionTo(InternalState.IDLE, reason);
     }
 
@@ -185,11 +210,24 @@
     void enter(@InternalState int state) {
         assert state == InternalState.UNDEFINED || state == InternalState.IDLE
                 || state == InternalState.LONG_PRESS_RECOGNIZED
+                || state == InternalState.RESOLVING_LONG_PRESS_RECOGNIZED
                 || state == InternalState.TAP_RECOGNIZED
                 || state == InternalState.SELECTION_CLEARED_RECOGNIZED;
         mPreviousState = mState;
         mState = state;
-
+        switch (state) {
+            case InternalState.LONG_PRESS_RECOGNIZED:
+                mSelectionType = SelectionType.LONG_PRESS;
+                break;
+            case InternalState.RESOLVING_LONG_PRESS_RECOGNIZED:
+                mSelectionType = SelectionType.RESOLVING_LONG_PRESS;
+                break;
+            case InternalState.TAP_RECOGNIZED:
+                mSelectionType = SelectionType.TAP;
+                break;
+            default:
+                mSelectionType = SelectionType.UNDETERMINED;
+        }
         notifyStartingWorkOn(mState);
         notifyFinishedWorkOn(mState);
     }
@@ -291,14 +329,16 @@
             case InternalState.START_SHOWING_TAP_UI:
                 mStateHandler.startShowingTapUi();
                 break;
-            case InternalState.SHOW_FULL_TAP_UI:
-                mStateHandler.showContextualSearchTapUi();
+            case InternalState.SHOW_RESOLVING_UI:
+                mStateHandler.showContextualSearchResolvingUi();
                 break;
             case InternalState.RESOLVING:
                 mStateHandler.resolveSearchTerm();
                 break;
             case InternalState.SHOWING_TAP_SEARCH:
                 break;
+            case InternalState.SHOWING_RESOLVED_LONG_PRESS_SEARCH:
+                break;
             default:
                 Log.w(TAG, "Warning: unexpected startWorkingOn " + String.valueOf(state));
                 break;
@@ -356,22 +396,34 @@
                 break;
             case InternalState.GATHERING_SURROUNDINGS:
                 // We gather surroundings for both Tap and Long-press in order to notify icing.
-                transitionTo(mPreviousState == InternalState.LONG_PRESS_RECOGNIZED
-                                ? InternalState.SHOWING_LONGPRESS_SEARCH
-                                : InternalState.DECIDING_SUPPRESSION);
+                if (mSelectionType == SelectionType.LONG_PRESS) {
+                    transitionTo(InternalState.SHOWING_LONGPRESS_SEARCH);
+                } else if (mSelectionType == SelectionType.RESOLVING_LONG_PRESS) {
+                    transitionTo(InternalState.SHOW_RESOLVING_UI);
+                } else {
+                    transitionTo(InternalState.DECIDING_SUPPRESSION);
+                }
                 break;
             case InternalState.DECIDING_SUPPRESSION:
                 transitionTo(InternalState.START_SHOWING_TAP_UI);
                 break;
             case InternalState.START_SHOWING_TAP_UI:
-                transitionTo(InternalState.SHOW_FULL_TAP_UI);
+                transitionTo(InternalState.SHOW_RESOLVING_UI);
                 break;
-            case InternalState.SHOW_FULL_TAP_UI:
-                transitionTo(mPolicy.shouldPreviousTapResolve() ? InternalState.RESOLVING
-                                                                : InternalState.SHOWING_TAP_SEARCH);
+            case InternalState.SHOW_RESOLVING_UI:
+                transitionTo(mPolicy.shouldPreviousGestureResolve()
+                                ? InternalState.RESOLVING
+                                : InternalState.SHOWING_TAP_SEARCH);
                 break;
             case InternalState.RESOLVING:
-                transitionTo(InternalState.SHOWING_TAP_SEARCH);
+                transitionTo(mSelectionType == SelectionType.TAP
+                                ? InternalState.SHOWING_TAP_SEARCH
+                                : InternalState.SHOWING_RESOLVED_LONG_PRESS_SEARCH);
+                break;
+            case InternalState.RESOLVING_LONG_PRESS_RECOGNIZED:
+                transitionTo(InternalState.GATHERING_SURROUNDINGS);
+                break;
+            case InternalState.SHOWING_RESOLVED_LONG_PRESS_SEARCH:
                 break;
             default:
                 Log.e(TAG, "The state " + String.valueOf(state) + " is not transitional!");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
index 6a442cbe..c68f58d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java
@@ -18,10 +18,10 @@
     void hideContextualSearchUi(@StateChangeReason int reason);
 
     /**
-     * Shows the Contextual Search user interface for a Tap.
-     * @see ContextualSearchInternalStateController.InternalState#SHOW_FULL_TAP_UI
+     * Shows the Contextual Search user interface for a resolving search.
+     * @see ContextualSearchInternalStateController.InternalState#SHOW_RESOLVING_UI
      */
-    void showContextualSearchTapUi();
+    void showContextualSearchResolvingUi();
 
     /**
      * Shows the Contextual Search user interface for a Long-press.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index a2224bb..33266622 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -21,6 +21,7 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayContentDelegate;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
@@ -467,7 +468,7 @@
             // If the user action was not a long-press, we should not delay before loading content.
             mShouldLoadDelayedSearch = false;
         }
-        if (isTap && mPolicy.shouldPreviousTapResolve()) {
+        if (isTap && mPolicy.shouldPreviousGestureResolve()) {
             // For a resolving Tap we'll figure out translation need after the Resolve.
         } else if (!TextUtils.isEmpty(selection)) {
             boolean shouldPrefetch = mPolicy.shouldPrefetchSearchResult();
@@ -521,9 +522,10 @@
     }
 
     @Override
-    public void startSearchTermResolutionRequest(String selection) {
+    public void startSearchTermResolutionRequest(String selection, boolean isRestrictedResolve) {
         WebContents baseWebContents = getBaseWebContents();
         if (baseWebContents != null && mContext != null && mContext.canResolve()) {
+            if (isRestrictedResolve) mContext.setRestrictedResolve();
             nativeStartSearchTermResolutionRequest(
                     mNativeContextualSearchManagerPtr, mContext, getBaseWebContents());
         } else {
@@ -785,7 +787,9 @@
         int selectionStartAdjust = resolvedSearchTerm.selectionStartAdjust();
         int selectionEndAdjust = resolvedSearchTerm.selectionEndAdjust();
         if ((selectionStartAdjust != 0 || selectionEndAdjust != 0)
-                && mSelectionController.getSelectionType() == SelectionType.TAP) {
+                && (mSelectionController.getSelectionType() == SelectionType.TAP
+                        || mSelectionController.getSelectionType()
+                                == SelectionType.RESOLVING_LONG_PRESS)) {
             String originalSelection = mContext == null ? null : mContext.getInitialSelectedWord();
             String currentSelection = mSelectionController.getSelectedText();
             if (currentSelection != null) currentSelection = currentSelection.trim();
@@ -1032,7 +1036,7 @@
 
                 // Record metrics for when the prefetched results became viewable.
                 if (mSearchRequest != null && mSearchRequest.wasPrefetch()) {
-                    boolean didResolve = mPolicy.shouldPreviousTapResolve();
+                    boolean didResolve = mPolicy.shouldPreviousGestureResolve();
                     mSearchPanel.onPanelNavigatedToPrefetchedSearch(didResolve);
                 }
             }
@@ -1387,10 +1391,22 @@
     }
 
     @Override
-    public void handleScroll() {
+    public void handleScrollStart() {
         if (mIsAccessibilityModeEnabled) return;
 
-        hideContextualSearch(StateChangeReason.BASE_PAGE_SCROLL);
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE)
+                || mSelectionController.getSelectionType() == SelectionType.TAP) {
+            hideContextualSearch(StateChangeReason.BASE_PAGE_SCROLL);
+        } else if (mSelectionController.getSelectionType() == SelectionType.RESOLVING_LONG_PRESS) {
+            mSearchPanel.makePanelNotVisible(StateChangeReason.BASE_PAGE_SCROLL);
+        }
+    }
+
+    @Override
+    public void handleScrollEnd() {
+        if (mSelectionController.getSelectionType() == SelectionType.RESOLVING_LONG_PRESS) {
+            mSearchPanel.showPanel(StateChangeReason.BASE_PAGE_SCROLL);
+        }
     }
 
     @Override
@@ -1462,6 +1478,13 @@
         mInternalStateController.enter(InternalState.TAP_RECOGNIZED);
     }
 
+    @Override
+    public void handleValidResolvingLongpress() {
+        if (mIsAccessibilityModeEnabled || !mPolicy.canResolveLongpress()) return;
+
+        mInternalStateController.enter(InternalState.RESOLVING_LONG_PRESS_RECOGNIZED);
+    }
+
     /**
      * Notifies this class that the selection has changed. This may be due to the user moving the
      * selection handles after a long-press, or after a Tap gesture has called selectWordAroundCaret
@@ -1484,6 +1507,8 @@
 
                 if (type == SelectionType.LONG_PRESS) {
                     mInternalStateController.enter(InternalState.LONG_PRESS_RECOGNIZED);
+                } else if (type == SelectionType.RESOLVING_LONG_PRESS) {
+                    mInternalStateController.enter(InternalState.RESOLVING_LONG_PRESS_RECOGNIZED);
                 }
             } else {
                 hideContextualSearch(StateChangeReason.INVALID_SELECTION);
@@ -1581,8 +1606,11 @@
                     }
                 };
 
-                boolean isTap = mSelectionController.getSelectionType() == SelectionType.TAP;
-                if (!isTap && mCouldSmartSelectionBeActive && mContext != null) {
+                boolean isResolvingGesture =
+                        mSelectionController.getSelectionType() == SelectionType.TAP
+                        || mSelectionController.getSelectionType()
+                                == SelectionType.RESOLVING_LONG_PRESS;
+                if (!isResolvingGesture && mCouldSmartSelectionBeActive && mContext != null) {
                     // If Smart Selection might be active we need to work around a race
                     // condition gathering surrounding text.  See https://crbug.com/773330.
                     // Instead we just return the selection which is good enough for the assistant.
@@ -1595,7 +1623,7 @@
                     return;
                 }
 
-                if (isTap && mPolicy.shouldPreviousTapResolve()) {
+                if (isResolvingGesture && mPolicy.shouldPreviousGestureResolve()) {
                     ContextualSearchInteractionPersister.PersistedInteraction interaction =
                             mInteractionRecorder.getInteractionPersister()
                                     .getAndClearPersistedInteraction();
@@ -1706,7 +1734,10 @@
 
                 String selection = mSelectionController.getSelectedText();
                 assert !TextUtils.isEmpty(selection);
-                mNetworkCommunicator.startSearchTermResolutionRequest(selection);
+                boolean isRestrictedResolve = mPolicy.isPrivacyAggressiveResolveEnabled()
+                        || mSelectionController.isAdjustedSelection();
+                mNetworkCommunicator.startSearchTermResolutionRequest(
+                        selection, isRestrictedResolve);
                 // If the we were unable to start the resolve, we've hidden the UI and set the
                 // context to null.
                 if (mContext == null || mSearchPanel == null) return;
@@ -1717,11 +1748,13 @@
             }
 
             @Override
-            public void showContextualSearchTapUi() {
-                mInternalStateController.notifyStartingWorkOn(InternalState.SHOW_FULL_TAP_UI);
-                showContextualSearch(StateChangeReason.TEXT_SELECT_TAP);
-                ContextualSearchUma.logRankerFeaturesAvailable(true);
-                mInternalStateController.notifyFinishedWorkOn(InternalState.SHOW_FULL_TAP_UI);
+            public void showContextualSearchResolvingUi() {
+                mInternalStateController.notifyStartingWorkOn(InternalState.SHOW_RESOLVING_UI);
+                boolean isTap = mSelectionController.getSelectionType() == SelectionType.TAP;
+                showContextualSearch(isTap ? StateChangeReason.TEXT_SELECT_TAP
+                                           : StateChangeReason.TEXT_SELECT_LONG_PRESS);
+                if (isTap) ContextualSearchUma.logRankerFeaturesAvailable(true);
+                mInternalStateController.notifyFinishedWorkOn(InternalState.SHOW_RESOLVING_UI);
             }
 
             @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
index 47dc301..a4b18ba2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java
@@ -16,8 +16,10 @@
      * Starts a Search Term Resolution request.
      * When the response comes back {@link #handleSearchTermResolutionResponse} will be called.
      * @param selection the current selected text.
+     * @param isRestrictedResolve Whether the resolution should be restricted to an exact match with
+     *        the given selection.
      */
-    void startSearchTermResolutionRequest(String selection);
+    void startSearchTermResolutionRequest(String selection, boolean isRestrictedResolve);
 
     /**
      * Handles a Search Term Resolution response.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
index 45521c2..a2dcdd94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java
@@ -13,6 +13,7 @@
 
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
@@ -133,21 +134,24 @@
             return false;
         }
 
-        // We never preload on long-press so users can cut & paste without hitting the servers.
-        return mSelectionController.getSelectionType() == SelectionType.TAP;
+        // We never preload on a regular long-press so users can cut & paste without hitting the
+        // servers.
+        return mSelectionController.getSelectionType() == SelectionType.TAP
+                || mSelectionController.getSelectionType() == SelectionType.RESOLVING_LONG_PRESS;
     }
 
     /**
-     * Returns whether the previous tap (the tap last counted) should resolve.
-     * @return Whether the previous tap should resolve.
+     * @return Whether the previous gesture should resolve.
      */
-    boolean shouldPreviousTapResolve() {
+    boolean shouldPreviousGestureResolve() {
         if (isMandatoryPromoAvailable()
                 || ContextualSearchFieldTrial.getSwitch(
                         ContextualSearchSwitch.IS_SEARCH_TERM_RESOLUTION_DISABLED)) {
             return false;
         }
 
+        if (isPrivacyAggressiveResolveEnabled()) return true;
+
         return (isPromoAvailable()
                        || (mContextualSearchPreferenceHelper != null
                                   && mContextualSearchPreferenceHelper.canThrottle()))
@@ -155,6 +159,11 @@
                 : true;
     }
 
+    /** @return Whether a long-press gesture can resolve. */
+    boolean canResolveLongpress() {
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE);
+    }
+
     /**
      * Returns whether surrounding context can be accessed by other systems or not.
      * @return Whether surroundings are available.
@@ -250,11 +259,29 @@
      *         is no exiting request.
      */
     boolean shouldCreateVerbatimRequest() {
+        if (isPrivacyAggressiveResolveEnabled()) return false;
+
         @SelectionType
         int selectionType = mSelectionController.getSelectionType();
         return (mSelectionController.getSelectedText() != null
                 && (selectionType == SelectionType.LONG_PRESS
-                || (selectionType == SelectionType.TAP && !shouldPreviousTapResolve())));
+                        || (selectionType == SelectionType.TAP
+                                && !shouldPreviousGestureResolve())));
+    }
+
+    /**
+     * Returns whether doing a privacy aggressive resolve is enabled (as opposed to privacy
+     * conservative).  When this is enabled, the selection is sent to the server immediately instead
+     * of waiting for the panel to be opened.  This allows the server to resolve the selection which
+     * will recognize entities, etc. and display those attributes in the Bar.
+     * @return Whether the privacy-aggressive behavior of immediately sending the selection to the
+     *         server is enabled.
+     */
+    boolean isPrivacyAggressiveResolveEnabled() {
+        return ContextualSearchFieldTrial.LONGPRESS_RESOLVE_PRIVACY_AGGRESSIVE.equals(
+                ChromeFeatureList.getFieldTrialParamByFeature(
+                        ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE,
+                        ContextualSearchFieldTrial.LONGPRESS_RESOLVE_PARAM_NAME));
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 8674c668..c226823 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -12,6 +12,7 @@
 import org.chromium.base.TimeUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial.ContextualSearchSetting;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial.ContextualSearchSwitch;
@@ -39,6 +40,7 @@
         int UNDETERMINED = 0;
         int TAP = 1;
         int LONG_PRESS = 2;
+        int RESOLVING_LONG_PRESS = 3;
     }
 
     private static final String TAG = "ContextualSearch";
@@ -93,15 +95,29 @@
     // The duration of the last tap gesture in milliseconds, or 0 if not set.
     private int mTapDurationMs = INVALID_DURATION;
 
+    /** Tracks whether we're currently clearing the selection to prevent recursion. */
+    private boolean mClearingSelection;
+
+    /**
+     * Whether the current selection has been adjusted or not.  If it has been adjusted we must
+     * request a resolve for this exact term rather than anything that overlaps as we get with
+     * normal expanding resolves.
+     */
+    private boolean mIsAdjustedSelection;
+
+    /** Whether the selection handles are currently showing. */
+    private boolean mAreSelectionHandlesShown;
+
     private class ContextualSearchGestureStateListener implements GestureStateListener {
         @Override
         public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
-            mHandler.handleScroll();
+            mHandler.handleScrollStart();
         }
 
         @Override
         public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
             mLastScrollTimeNs = System.nanoTime();
+            mHandler.handleScrollEnd();
         }
 
         @Override
@@ -224,12 +240,27 @@
     }
 
     /**
+     * Returns whether the current selection has been adjusted or not.
+     * If it has been adjusted we must request a resolve for this exact term rather than anything
+     * that overlaps as is the behavior with normal expanding resolves.
+     * @return Whether an exact word match is required in the resolve.
+     */
+    boolean isAdjustedSelection() {
+        return mIsAdjustedSelection;
+    }
+
+    /**
      * Clears the selection.
      */
     void clearSelection() {
+        if (mClearingSelection) return;
+
+        mClearingSelection = true;
         SelectionPopupController controller = getSelectionPopupController();
         if (controller != null) controller.clearSelection();
+        mHandler.handleSelectionCleared();
         resetSelectionStates();
+        mClearingSelection = false;
     }
 
     /**
@@ -285,27 +316,35 @@
         boolean shouldHandleSelection = false;
         switch (eventType) {
             case SelectionEventType.SELECTION_HANDLES_SHOWN:
+                mAreSelectionHandlesShown = true;
                 mWasTapGestureDetected = false;
-                mSelectionType = SelectionType.LONG_PRESS;
+                mSelectionType = ChromeFeatureList.isEnabled(
+                                         ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE)
+                        ? SelectionType.RESOLVING_LONG_PRESS
+                        : SelectionType.LONG_PRESS;
                 shouldHandleSelection = true;
                 SelectionPopupController controller = getSelectionPopupController();
                 if (controller != null) mSelectedText = controller.getSelectedText();
+                mIsAdjustedSelection = false;
                 break;
             case SelectionEventType.SELECTION_HANDLES_CLEARED:
+                mAreSelectionHandlesShown = false;
                 mHandler.handleSelectionDismissal();
+                clearSelection();
                 resetAllStates();
                 break;
             case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED:
                 shouldHandleSelection = mShouldHandleSelectionModification;
+                mIsAdjustedSelection = true;
                 break;
             default:
         }
 
+        mX = posXPix;
+        mY = posYPix;
         if (shouldHandleSelection) {
             if (mSelectedText != null) {
-                mX = posXPix;
-                mY = posYPix;
-                handleSelection(mSelectedText, SelectionType.LONG_PRESS);
+                handleSelection(mSelectedText, mSelectionType);
             }
         }
     }
@@ -344,6 +383,8 @@
         mSelectedText = null;
 
         mWasTapGestureDetected = false;
+        mIsAdjustedSelection = false;
+        mAreSelectionHandlesShown = false;
     }
 
     /**
@@ -409,7 +450,8 @@
         // been suppressed if each of the heuristics were satisfied.
         mHandler.handleMetricsForWouldSuppressTap(tapHeuristics);
 
-        boolean shouldSuppressTapBasedOnHeuristics = tapHeuristics.shouldSuppressTap();
+        boolean shouldSuppressTapBasedOnHeuristics =
+                tapHeuristics.shouldSuppressTap() || mAreSelectionHandlesShown;
         boolean shouldOverrideMlTapSuppression = tapHeuristics.shouldOverrideMlTapSuppression();
 
         // Make sure Tap Suppression features are consistent.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionHandler.java
index 5ebaab3e..e87b0a71 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionHandler.java
@@ -14,7 +14,12 @@
     /**
      * Handle a scroll event on the base page.
      */
-    public void handleScroll();
+    public void handleScrollStart();
+
+    /**
+     * Handle a scroll-ending event on the base page.
+     */
+    public void handleScrollEnd();
 
     /**
      * Handle the selection being cleared on the base page.
@@ -77,4 +82,9 @@
      * features to.
      */
     public void logNonHeuristicFeatures(ContextualSearchInteractionRecorder interactionRecorder);
+
+    /**
+     * Handles a long-press gesture that may make a server Resolve request to determine the search.
+     */
+    void handleValidResolvingLongpress();
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ResolvedSearchTerm.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ResolvedSearchTerm.java
index 2ac0b8fb4..aecf2a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ResolvedSearchTerm.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ResolvedSearchTerm.java
@@ -239,7 +239,7 @@
         return TextUtils.join(", ", sections);
     }
 
-    List<String> buildTextSections() {
+    private List<String> buildTextSections() {
         List<String> sections = new ArrayList<String>();
         if (mIsNetworkUnavailable) {
             sections.add("Network unavailable!");
@@ -247,8 +247,9 @@
             sections.add("ResponseCode:" + mResponseCode);
         } else {
             if (mDoPreventPreload) sections.add("Preventing preload!");
-            if (!TextUtils.isEmpty(mSearchTerm)) sections.add("Search for '" + mSearchTerm);
-            if (!TextUtils.isEmpty(mDisplayText)) sections.add("displayed as '" + mDisplayText);
+            if (!TextUtils.isEmpty(mSearchTerm)) sections.add("Search for '" + mSearchTerm + "'");
+            if (!TextUtils.isEmpty(mDisplayText))
+                sections.add("displayed as '" + mDisplayText + "'");
             if (!TextUtils.isEmpty(mMid)) sections.add("MID:'" + mMid);
             if (mSelectionStartAdjust != 0 || mSelectionEndAdjust != 0) {
                 sections.add(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
index 48f2dd8..e2ba055 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
@@ -137,8 +137,13 @@
         @Override
         public void onFocusChange(View v, boolean hasFocus) {
             if (hasFocus) {
-                getParent().requestChildRectangleOnScreen(
-                        ExploreSitesCategoryCardView.this, new Rect(), true);
+                // Ensures the whole category card is scrolled to view when a child site has focus.
+                // Immediate should be false so scrolling will not interfere with any existing
+                // scrollers running to make the view visible.
+                getParent().requestChildRectangleOnScreen(ExploreSitesCategoryCardView.this,
+                        new Rect(/* left= */ 0, /* top= */ 0, /* right= */ getWidth(),
+                                /* bottom= */ getHeight()),
+                        /* immediate= */ false);
             }
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
index 1161a33..bc63a9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.history;
 
 import android.content.res.Resources;
+import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.text.SpannableString;
@@ -40,11 +41,11 @@
     private static final String EMPTY_QUERY = "";
 
     private final SelectionDelegate<HistoryItem> mSelectionDelegate;
-    private final HistoryProvider mHistoryProvider;
     private final HistoryManager mHistoryManager;
     private final ArrayList<HistoryItemView> mItemViews;
     private final DefaultFaviconHelper mFaviconHelper;
     private RecyclerView mRecyclerView;
+    private @Nullable HistoryProvider mHistoryProvider;
 
     private View mPrivacyDisclaimerBottomSpace;
     private Button mClearBrowsingDataButton;
@@ -77,8 +78,11 @@
      * Called when the activity/native page is destroyed.
      */
     public void onDestroyed() {
-        mHistoryProvider.destroy();
         mIsDestroyed = true;
+
+        mHistoryProvider.destroy();
+        mHistoryProvider = null;
+
         mRecyclerView = null;
         mFaviconHelper.clearCache();
     }
@@ -243,6 +247,9 @@
 
     @Override
     public void onHistoryDeleted() {
+        // Return early if this call comes in after the activity/native page is destroyed.
+        if (mIsDestroyed) return;
+
         mSelectionDelegate.clearSelection();
         // TODO(twellington): Account for items that have been paged in due to infinite scroll.
         //                    This currently removes all items and re-issues a query.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java
index 5307cb0..4ac5e82 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java
@@ -12,29 +12,22 @@
  */
 public class WebApkSplashscreenMetrics implements SplashscreenObserver {
     private final long mShellApkLaunchTimeMs;
-    private long mSplashScreenShownTimeMs = -1;
 
     public WebApkSplashscreenMetrics(long shellApkLaunchTimeMs) {
         mShellApkLaunchTimeMs = shellApkLaunchTimeMs;
     }
 
     @Override
-    public void onSplashscreenHidden(long timestamp) {
+    public void onSplashscreenHidden(long startTimestamp, long endTimestamp) {
         if (mShellApkLaunchTimeMs == -1) return;
 
         if (UmaUtils.hasComeToForeground() && !UmaUtils.hasComeToBackground()) {
             // commit both shown/hidden histograms here because native may not be loaded when the
             // splashscreen is shown.
             WebApkUma.recordShellApkLaunchToSplashscreenVisible(
-                    mSplashScreenShownTimeMs - mShellApkLaunchTimeMs);
-            WebApkUma.recordShellApkLaunchToSplashscreenHidden(timestamp - mShellApkLaunchTimeMs);
+                    startTimestamp - mShellApkLaunchTimeMs);
+            WebApkUma.recordShellApkLaunchToSplashscreenHidden(
+                    endTimestamp - mShellApkLaunchTimeMs);
         }
     }
-
-    @Override
-    public void onSplashscreenShown(long timestamp) {
-        assert mSplashScreenShownTimeMs == -1;
-        if (mShellApkLaunchTimeMs == -1) return;
-        mSplashScreenShownTimeMs = timestamp;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
index aaaac3f..64ef412 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
@@ -237,6 +237,10 @@
 
         mDialogContainer = (ViewGroup) dialogContainerStub.inflate();
         mDialogContainer.setVisibility(View.GONE);
+
+        // Make sure clicks are not consumed by content beneath the container view.
+        mDialogContainer.setClickable(true);
+
         mContainerParent = (ViewGroup) mDialogContainer.getParent();
         // The default sibling view is the next view of the dialog container stub in main.xml and
         // should not be removed from its parent.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ProvidedByWebApkSplashDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/ProvidedByWebApkSplashDelegate.java
index 073a58b..0fb59ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ProvidedByWebApkSplashDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/ProvidedByWebApkSplashDelegate.java
@@ -45,7 +45,8 @@
     }
 
     @Override
-    public void onSplashHidden(Tab tab) {
+    public void onSplashHidden(Tab tab, @SplashController.SplashHidesReason int reason,
+            long startTimestamp, long endTimestamp) {
         // TODO(pkotwicz) implement.
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SameActivityWebappSplashDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SameActivityWebappSplashDelegate.java
index 197d832..295ee12 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SameActivityWebappSplashDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SameActivityWebappSplashDelegate.java
@@ -15,6 +15,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -29,6 +30,9 @@
  * activity).
  */
 public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeInitObserver {
+    public static final String HISTOGRAM_SPLASHSCREEN_DURATION = "Webapp.Splashscreen.Duration";
+    public static final String HISTOGRAM_SPLASHSCREEN_HIDES = "Webapp.Splashscreen.Hides";
+
     private Activity mActivity;
     private ActivityLifecycleDispatcher mLifecycleDispatcher;
     private TabObserverRegistrar mTabObserverRegistrar;
@@ -98,7 +102,8 @@
     }
 
     @Override
-    public void onSplashHidden(Tab tab) {
+    public void onSplashHidden(Tab tab, @SplashController.SplashHidesReason int reason,
+            long startTimestamp, long endTimestamp) {
         if (mWebApkNetworkErrorObserver != null) {
             mTabObserverRegistrar.unregisterTabObserver(mWebApkNetworkErrorObserver);
             tab.removeObserver(mWebApkNetworkErrorObserver);
@@ -106,6 +111,8 @@
         }
         mLifecycleDispatcher.unregister(SameActivityWebappSplashDelegate.this);
 
+        recordSplashHiddenUma(reason, startTimestamp, endTimestamp);
+
         mActivity = null;
     }
 
@@ -141,6 +148,16 @@
                 (splashImage != null));
     }
 
+    /** Called once the splash screen is hidden to record UMA metrics. */
+    private void recordSplashHiddenUma(@SplashController.SplashHidesReason int reason,
+            long startTimestamp, long endTimestamp) {
+        RecordHistogram.recordEnumeratedHistogram(HISTOGRAM_SPLASHSCREEN_HIDES, reason,
+                SplashController.SplashHidesReason.NUM_ENTRIES);
+
+        RecordHistogram.recordMediumTimesHistogram(
+                HISTOGRAM_SPLASHSCREEN_DURATION, endTimestamp - startTimestamp);
+    }
+
     /**
      * Records splash screen UMA metrics.
      * @param resources
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
index c6052d0e0..08ab76cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
@@ -13,7 +13,6 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.WarmupManager;
 import org.chromium.chrome.browser.compositor.CompositorView;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
@@ -62,9 +61,6 @@
         int NUM_ENTRIES = 4;
     }
 
-    public static final String HISTOGRAM_SPLASHSCREEN_DURATION = "Webapp.Splashscreen.Duration";
-    public static final String HISTOGRAM_SPLASHSCREEN_HIDES = "Webapp.Splashscreen.Hides";
-
     private final TabObserverRegistrar mTabObserverRegistrar;
 
     private SplashDelegate mDelegate;
@@ -97,8 +93,6 @@
 
         mSplashView = mDelegate.buildSplashView(webappInfo);
         mParentView.addView(mSplashView);
-
-        notifySplashscreenVisible(mSplashShownTimestamp);
     }
 
     /**
@@ -179,24 +173,14 @@
         long splashHiddenTimestamp = SystemClock.elapsedRealtime();
         recordTraceEventsFinishedHidingSplash();
 
-        mDelegate.onSplashHidden(tab);
-        recordSplashHiddenUma(reason, splashHiddenTimestamp);
-        notifySplashscreenHidden(splashHiddenTimestamp);
+        assert mSplashShownTimestamp != 0;
+        mDelegate.onSplashHidden(tab, reason, mSplashShownTimestamp, splashHiddenTimestamp);
+        notifySplashscreenHidden(mSplashShownTimestamp, splashHiddenTimestamp);
 
         mDelegate = null;
         mSplashView = null;
     }
 
-    /** Called once the splash screen is hidden to record UMA metrics. */
-    private void recordSplashHiddenUma(@SplashHidesReason int reason, long splashHiddenTimestamp) {
-        RecordHistogram.recordEnumeratedHistogram(
-                HISTOGRAM_SPLASHSCREEN_HIDES, reason, SplashHidesReason.NUM_ENTRIES);
-
-        assert mSplashShownTimestamp != 0;
-        RecordHistogram.recordMediumTimesHistogram(
-                HISTOGRAM_SPLASHSCREEN_DURATION, splashHiddenTimestamp - mSplashShownTimestamp);
-    }
-
     /**
      * Register an observer for the splashscreen hidden/visible events.
      */
@@ -205,21 +189,15 @@
     }
 
     /**
-     * Deegister an observer for the splashscreen hidden/visible events.
+     * Deregister an observer for the splashscreen hidden/visible events.
      */
     public void removeObserver(SplashscreenObserver observer) {
         mObservers.removeObserver(observer);
     }
 
-    private void notifySplashscreenVisible(long timestamp) {
+    private void notifySplashscreenHidden(long startTimestamp, long endTimestmap) {
         for (SplashscreenObserver observer : mObservers) {
-            observer.onSplashscreenShown(timestamp);
-        }
-    }
-
-    private void notifySplashscreenHidden(long timestamp) {
-        for (SplashscreenObserver observer : mObservers) {
-            observer.onSplashscreenHidden(timestamp);
+            observer.onSplashscreenHidden(startTimestamp, endTimestmap);
         }
         mObservers.clear();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashDelegate.java
index 8e83782..47915f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashDelegate.java
@@ -13,8 +13,15 @@
     /** Builds the splash view. */
     View buildSplashView(WebappInfo webappInfo);
 
-    /** Called when splash screen has been hidden. */
-    void onSplashHidden(Tab tab);
+    /**
+     * Called when splash screen has been hidden.
+     * @param tab
+     * @param reason Reason that the splash screen was hidden.
+     * @param startTimestamp Time that the splash screen was shown.
+     * @param endTimestap Time that the splash screen was hidden.
+     */
+    void onSplashHidden(Tab tab, @SplashController.SplashHidesReason int reason,
+            long startTimestamp, long endTimestamp);
 
     /** Returns whether to wait for a subsequent page load to hide the splash screen. */
     boolean shouldWaitForSubsequentPageLoadToHideSplash();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashscreenObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashscreenObserver.java
index f79e3d2..310a75028 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashscreenObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashscreenObserver.java
@@ -9,14 +9,9 @@
  */
 public interface SplashscreenObserver {
     /**
-     * Called when the splashscreen is shown.
-     * @param timestamp Time that the splash screen was shown.
+     * Called when the splash screen is hidden.
+     * @param startTimestamp Time that the splash screen was shown.
+     * @param endTimestap Time that the splash screen was hidden.
      */
-    void onSplashscreenShown(long timestamp);
-
-    /**
-     * Called when the splashscreen is hidden.
-     * @param timestamp Time that the splash screen was hidden.
-     */
-    void onSplashscreenHidden(long timestamp);
+    void onSplashscreenHidden(long startTimestamp, long endTimestamp);
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
index 1e00f24..dc5a7ed 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
@@ -310,7 +310,7 @@
             mManagerTest.clickNode(getNodeId());
             mManagerTest.waitForSelectionToBe(getSearchTerm());
 
-            if (mPolicy.shouldPreviousTapResolve()) {
+            if (mPolicy.shouldPreviousGestureResolve()) {
                 // Now wait for the Search Term Resolution to start.
                 mManagerTest.waitForSearchTermResolutionToStart(this);
 
@@ -422,7 +422,7 @@
             mManagerTest.clickNode(getNodeId());
             mManagerTest.waitForSelectionToBe(getSearchTerm());
 
-            if (mPolicy.shouldPreviousTapResolve()) {
+            if (mPolicy.shouldPreviousGestureResolve()) {
                 // Now wait for the Search Term Resolution to start.
                 mManagerTest.waitForSearchTermResolutionToStart(this);
             } else {
@@ -619,7 +619,7 @@
     //============================================================================================
 
     @Override
-    public void startSearchTermResolutionRequest(String selection) {
+    public void startSearchTermResolutionRequest(String selection, boolean isRestrictedResolve) {
         mLoadedUrl = null;
         mSearchTermRequested = selection;
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
index 23981a92..95454f5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateControllerWrapper.java
@@ -19,7 +19,7 @@
             CollectionUtil.newArrayList(InternalState.TAP_RECOGNIZED,
                     InternalState.TAP_GESTURE_COMMIT, InternalState.GATHERING_SURROUNDINGS,
                     InternalState.DECIDING_SUPPRESSION, InternalState.START_SHOWING_TAP_UI,
-                    InternalState.SHOW_FULL_TAP_UI, InternalState.RESOLVING);
+                    InternalState.SHOW_RESOLVING_UI, InternalState.RESOLVING);
     static final List<Integer> EXPECTED_LONGPRESS_SEQUENCE =
             CollectionUtil.newArrayList(InternalState.LONG_PRESS_RECOGNIZED,
                     InternalState.GATHERING_SURROUNDINGS, InternalState.SHOWING_LONGPRESS_SEARCH);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
index 06731630..e8d2ab3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTapEventTest.java
@@ -15,6 +15,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -97,7 +98,8 @@
         }
 
         @Override
-        public void startSearchTermResolutionRequest(String selection) {
+        public void startSearchTermResolutionRequest(
+                String selection, boolean isRestrictedResolve) {
             // Skip native calls and immediately "resolve" the search term.
             onSearchTermResolutionResponse(true, 200, selection, selection, "", "", false, 0, 10,
                     "", "", "", "", QuickActionCategory.NONE, 0, "", "", 0);
@@ -319,7 +321,8 @@
     @SmallTest
     @Feature({"ContextualSearch"})
     @Restriction(Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE)
-    public void testTapProcessIsRobustWhenSelectionGetsCleared() throws InterruptedException {
+    @DisabledTest(message = "See https://crbug.com/959040")
+    public void testTapProcessIsRobustWhenSelectionGetsClearedDLD() throws InterruptedException {
         Assert.assertEquals(mPanelManager.getRequestPanelShowCount(), 0);
 
         // Fake a Tap event.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java
index 34588e8..d1560230 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java
@@ -16,6 +16,7 @@
 
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertTrue;
 
 import static org.chromium.chrome.browser.modaldialog.ModalDialogTestUtils.checkCurrentPresenter;
 import static org.chromium.chrome.browser.modaldialog.ModalDialogTestUtils.checkDialogDismissalCause;
@@ -50,9 +51,11 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.OmniboxTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -90,6 +93,7 @@
 
     private ChromeTabbedActivity mActivity;
     private ModalDialogManager mManager;
+    private TabModalPresenter mTabModalPresenter;
     private TestObserver mTestObserver;
     private Integer mExpectedDismissalCause;
 
@@ -103,9 +107,8 @@
                 .getToolbarLayoutForTesting()
                 .getLocationBar()
                 .addUrlFocusChangeListener(mTestObserver);
-        TabModalPresenter presenter =
-                (TabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB);
-        presenter.disableAnimationForTest();
+        mTabModalPresenter = (TabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB);
+        mTabModalPresenter.disableAnimationForTest();
     }
 
     @Test
@@ -123,7 +126,7 @@
         final ViewGroup containerParent = presenter.getContainerParentForTest();
 
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+            assertTrue(containerParent.indexOfChild(dialogContainer)
                     > containerParent.indexOfChild(controlContainer));
         });
 
@@ -133,7 +136,7 @@
         OmniboxTestUtils.toggleUrlBarFocus(urlBar, true);
         mTestObserver.onUrlFocusChangedCallback.waitForCallback(callCount);
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+            assertTrue(containerParent.indexOfChild(dialogContainer)
                     < containerParent.indexOfChild(controlContainer));
         });
 
@@ -142,7 +145,7 @@
         OmniboxTestUtils.toggleUrlBarFocus(urlBar, false);
         mTestObserver.onUrlFocusChangedCallback.waitForCallback(callCount);
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertTrue(containerParent.indexOfChild(dialogContainer)
+            assertTrue(containerParent.indexOfChild(dialogContainer)
                     > containerParent.indexOfChild(controlContainer));
         });
 
@@ -397,6 +400,36 @@
     @Test
     @SmallTest
     @Feature({"ModalDialog"})
+    public void testDismiss_CancelOnTouchOutside() throws Exception {
+        PropertyModel dialog1 = createDialog(mActivity, "1", null);
+
+        // Show a tab modal dialog and verify it shows.
+        showDialog(mManager, dialog1, ModalDialogType.TAB);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(hasDescendant(withText("1"))));
+        checkCurrentPresenter(mManager, ModalDialogType.TAB);
+
+        // Click dialog container and verify the dialog is not dismissed.
+        final View dialogContainer = mTabModalPresenter.getDialogContainerForTest();
+        assertTrue(dialogContainer.isClickable());
+        TestThreadUtils.runOnUiThreadBlocking(dialogContainer::performClick);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(hasDescendant(withText("1"))));
+        checkCurrentPresenter(mManager, ModalDialogType.TAB);
+
+        // Enable cancel on touch outside and verify the dialog is dismissed.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> dialog1.set(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true));
+        assertTrue(dialogContainer.isClickable());
+        TestThreadUtils.runOnUiThreadBlocking(dialogContainer::performClick);
+        onView(withId(R.id.tab_modal_dialog_container))
+                .check(matches(not(hasDescendant(withText("1")))));
+        checkCurrentPresenter(mManager, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"ModalDialog"})
     public void testDismiss_DismissalCause_BackPressed() throws Exception {
         PropertyModel dialog1 = createDialog(mActivity, "1", mTestObserver);
         mExpectedDismissalCause = DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE;
@@ -449,7 +482,7 @@
 
     private void checkBrowserControls(boolean restricted) {
         if (restricted) {
-            Assert.assertTrue("All tabs should be obscured", mActivity.isViewObscuringAllTabs());
+            assertTrue("All tabs should be obscured", mActivity.isViewObscuringAllTabs());
             onView(allOf(isDisplayed(), withId(R.id.menu_button))).check(matches(not(isEnabled())));
         } else {
             Assert.assertFalse("Tabs shouldn't be obscured", mActivity.isViewObscuringAllTabs());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
index 8d50461..6d15f2e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenTest.java
@@ -206,10 +206,11 @@
                         SameActivityWebappUmaCache.HISTOGRAM_SPLASHSCREEN_ICON_SIZE, 50));
 
         // DURATION and HIDES UMA should not have been recorded yet.
-        Assert.assertFalse(
-                hasHistogramEntry(SplashController.HISTOGRAM_SPLASHSCREEN_DURATION, 3000));
+        Assert.assertFalse(hasHistogramEntry(
+                SameActivityWebappSplashDelegate.HISTOGRAM_SPLASHSCREEN_DURATION, 3000));
         Assert.assertEquals(0,
-                getHistogramTotalCountFor(SplashController.HISTOGRAM_SPLASHSCREEN_HIDES,
+                getHistogramTotalCountFor(
+                        SameActivityWebappSplashDelegate.HISTOGRAM_SPLASHSCREEN_HIDES,
                         SplashController.SplashHidesReason.NUM_ENTRIES));
     }
 
@@ -225,10 +226,11 @@
         mActivityTestRule.waitUntilSplashscreenHides();
 
         // DURATION and HIDES should now have a value.
-        Assert.assertTrue(
-                hasHistogramEntry(SplashController.HISTOGRAM_SPLASHSCREEN_DURATION, 10000));
+        Assert.assertTrue(hasHistogramEntry(
+                SameActivityWebappSplashDelegate.HISTOGRAM_SPLASHSCREEN_DURATION, 10000));
         Assert.assertEquals(1,
-                getHistogramTotalCountFor(SplashController.HISTOGRAM_SPLASHSCREEN_HIDES,
+                getHistogramTotalCountFor(
+                        SameActivityWebappSplashDelegate.HISTOGRAM_SPLASHSCREEN_HIDES,
                         SplashController.SplashHidesReason.NUM_ENTRIES));
 
         // The other UMA records should not have changed.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
index 3059b90..835e69e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateTest.java
@@ -35,9 +35,9 @@
         }
 
         @Override
-        public void showContextualSearchTapUi() {
+        public void showContextualSearchResolvingUi() {
             mDidShow = true;
-            stubForWorkOnState(InternalState.SHOW_FULL_TAP_UI);
+            stubForWorkOnState(InternalState.SHOW_RESOLVING_UI);
         }
 
         @Override
@@ -131,11 +131,11 @@
     }
 
     private void mocksForTap() {
-        when(mMockedPolicy.shouldPreviousTapResolve()).thenReturn(true);
+        when(mMockedPolicy.shouldPreviousGestureResolve()).thenReturn(true);
     }
 
     private void mocksForNonResolvingTap() {
-        when(mMockedPolicy.shouldPreviousTapResolve()).thenReturn(false);
+        when(mMockedPolicy.shouldPreviousGestureResolve()).thenReturn(false);
     }
 
     private void mocksForLongpress() {
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index 215115b..858bdc2 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -79,8 +79,8 @@
 
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
     defined(OS_CHROMEOS)
+#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"  // nogncheck
 #include "chrome/browser/ui/webui/discards/discards.mojom.h"
-#include "services/resource_coordinator/public/mojom/webui_graph_dump.mojom.h"  // nogncheck
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -256,7 +256,7 @@
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
     defined(OS_CHROMEOS)
                     mojom::DiscardsDetailsProvider,
-                    resource_coordinator::mojom::WebUIGraphDump,
+                    performance_manager::mojom::WebUIGraphDump,
 #endif
                     mojom::BluetoothInternalsHandler,
                     mojom::InterventionsInternalsPageHandler,
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index c142049a..5db978d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1834,6 +1834,7 @@
     "//chrome/browser/metrics/variations:chrome_ui_string_overrider_factory",
     "//chrome/browser/net:probe_message_proto",
     "//chrome/browser/notifications",
+    "//chrome/browser/performance_manager:mojo_bindings",
     "//chrome/browser/profiling_host",
     "//chrome/browser/push_messaging:budget_proto",
     "//chrome/browser/resource_coordinator:mojo_bindings",
@@ -5065,6 +5066,7 @@
     ":chrome_internal_resources_gen",
     "//chrome/browser/engagement:mojo_bindings_js",
     "//chrome/browser/media:mojo_bindings_js",
+    "//chrome/browser/performance_manager:mojo_bindings_js",
     "//chrome/browser/resources/ssl/ssl_error_assistant:make_ssl_error_assistant_protobuf",
     "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings_js",
     "//chrome/browser/ui/webui/downloads:mojo_bindings_js",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9e02d30..6fda5c2 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -899,6 +899,19 @@
     contextual_search::kContextualCardsSimplifiedServerWithDiagnosticChar};
 const FeatureEntry::FeatureVariation kSimplifiedServerVariations[] = {
     {"and allow all CoCa cards", &kSimplifiedServerAllCocaCards, 1, nullptr}};
+
+const FeatureEntry::FeatureParam kLongpressResolveHideOnScroll = {
+    contextual_search::kLongpressResolveParamName,
+    contextual_search::kLongpressResolveHideOnScroll};
+const FeatureEntry::FeatureParam kLongpressResolvePrivacyAggressive = {
+    contextual_search::kLongpressResolveParamName,
+    contextual_search::kLongpressResolvePrivacyAggressive};
+const FeatureEntry::FeatureVariation kLongpressResolveVariations[] = {
+    {"and hide on scroll", &kLongpressResolveHideOnScroll, 1, nullptr},
+    {"and allow privacy-aggressive behavior",
+     &kLongpressResolvePrivacyAggressive, 1, nullptr},
+};
+
 #endif  // defined(OS_ANDROID)
 
 const FeatureEntry::FeatureParam kResamplingInputEventsLSQEnabled[] = {
@@ -1091,6 +1104,16 @@
      flag_descriptions::kContextualSearchDefinitionsName,
      flag_descriptions::kContextualSearchDefinitionsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kContextualSearchDefinitions)},
+
+    {"contextual-search-longpress-resolve",
+     flag_descriptions::kContextualSearchLongpressResolveName,
+     flag_descriptions::kContextualSearchLongpressResolveDescription,
+     kOsAndroid,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(
+         chrome::android::kContextualSearchLongpressResolve,
+         kLongpressResolveVariations,
+         "ContextualSearchLongpressResolve")},
+
     {"contextual-search-ml-tap-suppression",
      flag_descriptions::kContextualSearchMlTapSuppressionName,
      flag_descriptions::kContextualSearchMlTapSuppressionDescription,
@@ -1104,6 +1127,7 @@
      flag_descriptions::kContextualSearchSecondTapName,
      flag_descriptions::kContextualSearchSecondTapDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kContextualSearchSecondTap)},
+
     {"contextual-search-simplified-server",
      flag_descriptions::kContextualSearchSimplifiedServerName,
      flag_descriptions::kContextualSearchSimplifiedServerDescription,
@@ -1112,6 +1136,7 @@
          chrome::android::kContextualSearchSimplifiedServer,
          kSimplifiedServerVariations,
          "ContextualSearchSimplifiedServer")},
+
     {"contextual-search-translation-model",
      flag_descriptions::kContextualSearchTranslationModelName,
      flag_descriptions::kContextualSearchTranslationModelDescription,
@@ -3487,6 +3512,12 @@
      flag_descriptions::kAshEnablePipRoundedCornersName,
      flag_descriptions::kAshEnablePipRoundedCornersDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kPipRoundedCorners)},
+
+    {"ash-swap-side-volume-buttons-for-orientation",
+     flag_descriptions::kAshSwapSideVolumeButtonsForOrientationName,
+     flag_descriptions::kAshSwapSideVolumeButtonsForOrientationDescription,
+     kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kSwapSideVolumeButtonsForOrientation)},
 #endif  // defined(OS_CHROMEOS)
     {"google-password-manager", flag_descriptions::kGooglePasswordManagerName,
      flag_descriptions::kGooglePasswordManagerDescription, kOsAll,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 0c182e4..df2c78f 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -119,6 +119,7 @@
     &kCommandLineOnNonRooted,
     &kContentSuggestionsScrollToLoad,
     &kContextualSearchDefinitions,
+    &kContextualSearchLongpressResolve,
     &kContextualSearchMlTapSuppression,
     &kContextualSearchSecondTap,
     &kContextualSearchSimplifiedServer,
@@ -334,6 +335,9 @@
 const base::Feature kContextualSearchDefinitions{
     "ContextualSearchDefinitions", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kContextualSearchLongpressResolve{
+    "ContextualSearchLongpressResolve", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kContextualSearchMlTapSuppression{
     "ContextualSearchMlTapSuppression", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index d7bcad4..515e35c 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -47,6 +47,7 @@
 extern const base::Feature kCommandLineOnNonRooted;
 extern const base::Feature kContentSuggestionsScrollToLoad;
 extern const base::Feature kContextualSearchDefinitions;
+extern const base::Feature kContextualSearchLongpressResolve;
 extern const base::Feature kContextualSearchMlTapSuppression;
 extern const base::Feature kContextualSearchSecondTap;
 extern const base::Feature kContextualSearchSimplifiedServer;
diff --git a/chrome/browser/android/contextualsearch/contextual_search_context.cc b/chrome/browser/android/contextualsearch/contextual_search_context.cc
index 84cdebf..e84132b 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_context.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_context.cc
@@ -149,6 +149,17 @@
   return end_offset;
 }
 
+void ContextualSearchContext::RestrictResolve(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  // TODO(donnd): improve on this cheap implementation by sending this bit to
+  // the server instead of destroying our valuable context!
+  int start = this->start_offset;
+  int end = this->end_offset;
+  SetSelectionSurroundings(0, end - start,
+                           this->surrounding_text.substr(start, end - start));
+}
+
 base::android::ScopedJavaLocalRef<jstring>
 ContextualSearchContext::DetectLanguage(
     JNIEnv* env,
diff --git a/chrome/browser/android/contextualsearch/contextual_search_context.h b/chrome/browser/android/contextualsearch/contextual_search_context.h
index efdf5c5..a2080be 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_context.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_context.h
@@ -92,6 +92,11 @@
   int64_t GetPreviousEventId() const;
   int GetPreviousEventResults() const;
 
+  // Causes the next resolve request to be for an exact match instead of an
+  // expandable term.
+  void RestrictResolve(JNIEnv* env,
+                       const base::android::JavaParamRef<jobject>& obj);
+
   // Detects the language of the context using CLD from the translate utility.
   base::android::ScopedJavaLocalRef<jstring> DetectLanguage(
       JNIEnv* env,
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 232a7ee..3b4902bf 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -75,7 +75,7 @@
         <include name="IDR_DISCARDS_MOJO_PUBLIC_BASE_PROCESS_ID_MOJOM_LITE_JS" file="${root_gen_dir}\mojo\public\mojom\base\process_id.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
         <include name="IDR_DISCARDS_SORTED_TABLE_BEHAVIOR_HTML" file="resources\discards\sorted_table_behavior.html" compress="gzip" allowexternalscript="true" type="BINDATA" />
         <include name="IDR_DISCARDS_SORTED_TABLE_BEHAVIOR_JS" file="resources\discards\sorted_table_behavior.js" compress="gzip" type="BINDATA" />
-        <include name="IDR_DISCARDS_WEBUI_GRAPH_DUMP_MOJOM_LITE_JS" file="${root_gen_dir}\services\resource_coordinator\public\mojom\webui_graph_dump.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
+        <include name="IDR_DISCARDS_WEBUI_GRAPH_DUMP_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\performance_manager\webui_graph_dump.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
       </if>
       <if expr="is_win or is_macosx or (is_linux and not is_chromeos)">
         <include name="IDR_BROWSER_SWITCHER_APP_HTML" file="resources\browser_switcher\app.html" compress="gzip" allowexternalscript="true" type="BINDATA" />
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7d2f5a17..222428c 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -181,6 +181,11 @@
     "expiry_milestone": 75
   },
   {
+    "name": "ash-swap-side-volume-buttons-for-orientation",
+    "owners": [ "minch" ],
+    "expiry_milestone": 78
+  },
+  {
     "name": "auto-fetch-on-net-error-page",
     "owners": [ "harringtond", "offline-dev" ],
     "expiry_milestone": 82
@@ -390,6 +395,11 @@
     "expiry_milestone": 74
   },
   {
+    "name": "contextual-search-longpress-resolve",
+    "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
+    "expiry_milestone": 78
+  },
+  {
     "name": "contextual-search-ml-tap-suppression",
     "owners": [ "//chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/OWNERS" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 05cf00c..43b4c9f 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2174,6 +2174,13 @@
     "Enables tap-activated contextual definitions of words on a page to be "
     "presented in the caption of the Tap to Search Bar.";
 
+const char kContextualSearchLongpressResolveName[] =
+    "Contextual Search long-press Resolves";
+const char kContextualSearchLongpressResolveDescription[] =
+    "Enables communicating with Google servers when a long-press gesture is "
+    "recognized under some privacy-limited conditions.  The page context data "
+    " sent to Google is potentially privacy sensitive!";
+
 const char kContextualSearchMlTapSuppressionName[] =
     "Contextual Search ML tap suppression";
 const char kContextualSearchMlTapSuppressionDescription[] =
@@ -2917,6 +2924,13 @@
 const char kAshEnablePipRoundedCornersDescription[] =
     "Enable rounded corners on the Picture-in-Picture window.";
 
+const char kAshSwapSideVolumeButtonsForOrientationName[] =
+    "Swap side volume buttons to match screen orientation.";
+const char kAshSwapSideVolumeButtonsForOrientationDescription[] =
+    "Make the side volume button that's closer to the top/right always "
+    "increase the volume and the button that's closer to the bottom/left "
+    "always decrease the volume.";
+
 const char kAshEnableUnifiedDesktopName[] = "Unified desktop mode";
 const char kAshEnableUnifiedDesktopDescription[] =
     "Enable unified desktop mode which allows a window to span multiple "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 33d7873..f644266 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1306,6 +1306,9 @@
 extern const char kContextualSearchDefinitionsName[];
 extern const char kContextualSearchDefinitionsDescription[];
 
+extern const char kContextualSearchLongpressResolveName[];
+extern const char kContextualSearchLongpressResolveDescription[];
+
 extern const char kContextualSearchMlTapSuppressionName[];
 extern const char kContextualSearchMlTapSuppressionDescription[];
 
@@ -1753,6 +1756,9 @@
 extern const char kAshEnablePipRoundedCornersName[];
 extern const char kAshEnablePipRoundedCornersDescription[];
 
+extern const char kAshSwapSideVolumeButtonsForOrientationName[];
+extern const char kAshSwapSideVolumeButtonsForOrientationDescription[];
+
 extern const char kAshEnableUnifiedDesktopName[];
 extern const char kAshEnableUnifiedDesktopDescription[];
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
index f2cde5fa..051d2a1 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
@@ -360,6 +360,11 @@
   const MediaSinkInternal* sink = media_sink_service_->GetSinkByRoute(route);
   CHECK(sink);
 
+  for (auto& client : activity->connected_clients()) {
+    client.second->SendMessageToClient(
+        CreateReceiverActionStopMessage(client.first, *sink, hash_token_));
+  }
+
   activity->SendStopSessionMessageToReceiver(
       base::nullopt,  // TODO(jrw): Get the real client ID.
       hash_token_, std::move(callback));
@@ -437,6 +442,7 @@
 
   // If |activity| is null, we have discovered a non-local activity.
   if (activity_it == activities_.end()) {
+    // TODO(crbug.com/954797): Test this case.
     AddNonLocalActivityRecord(sink, session);
     NotifyAllOnRoutesUpdated();
     return;
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.h b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
index 87a7a60..5313be0 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
@@ -19,6 +19,7 @@
 #include "chrome/browser/media/router/providers/cast/cast_session_tracker.h"
 #include "chrome/common/media_router/mojo/media_router.mojom.h"
 #include "chrome/common/media_router/providers/cast/cast_media_source.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "url/origin.h"
 
 namespace cast_channel {
@@ -118,6 +119,7 @@
       mojom::MediaRouteProvider::TerminateRouteCallback callback) override;
 
  private:
+  friend class CastActivityManagerTest;
   using ActivityMap =
       base::flat_map<MediaRoute::Id, std::unique_ptr<CastActivityRecordBase>>;
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
index a9810dd..9e8308e 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager_unittest.cc
@@ -5,29 +5,44 @@
 #include "chrome/browser/media/router/providers/cast/cast_activity_manager.h"
 
 #include <memory>
+#include <string>
 #include <tuple>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/mock_callback.h"
 #include "base/test/values_test_util.h"
+#include "base/values.h"
 #include "chrome/browser/media/router/data_decoder_util.h"
+#include "chrome/browser/media/router/providers/cast/cast_session_client.h"
+#include "chrome/browser/media/router/providers/cast/mock_cast_activity_record.h"
+#include "chrome/browser/media/router/providers/common/buffered_message_sender.h"
 #include "chrome/browser/media/router/test/mock_mojo_media_router.h"
 #include "chrome/browser/media/router/test/test_helper.h"
 #include "chrome/common/media_router/test/test_helper.h"
 #include "components/cast_channel/cast_test_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "services/data_decoder/data_decoder_service.h"
 #include "services/data_decoder/public/cpp/testing_json_parser.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::test::IsJson;
-using base::test::ParseJsonDeprecated;
+using base::test::ParseJson;
 using testing::_;
+using testing::AnyNumber;
+using testing::ByRef;
+using testing::ElementsAre;
 using testing::IsEmpty;
+using testing::Not;
 using testing::Return;
 using testing::WithArg;
 
@@ -37,79 +52,43 @@
 constexpr int kChannelId = 42;
 constexpr char kOrigin[] = "https://google.com";
 constexpr int kTabId = 1;
-constexpr char kSource1[] = "cast:ABCDEFGH?clientId=theClientId";
-constexpr char kSource2[] = "cast:BBBBBBBB?clientId=theClientId";
-constexpr char kReceiverStatus[] = R"({
+constexpr char kAppId1[] = "ABCDEFGH";
+constexpr char kAppId2[] = "BBBBBBBB";
+
+std::string MakeSourceId(const std::string& app_id = kAppId1) {
+  return "cast:" + app_id + "?clientId=theClientId";
+}
+
+base::Value MakeReceiverStatus(const std::string& app_id,
+                               bool update_display_name = false) {
+  return ParseJson(R"({
         "applications": [{
-          "appId": "ABCDEFGH",
-          "displayName": "App display name",
+          "appId": ")" +
+                   app_id +
+                   R"(",
+          "displayName": "theDisplayName)" +
+                   std::string(update_display_name ? "1" : "2") + R"(",
           "namespaces": [
             {"name": "urn:x-cast:com.google.cast.media"},
-            {"name": "urn:x-cast:com.google.foo"}
+            {"name": "urn:x-cast:com.google.foo"},
           ],
           "sessionId": "theSessionId",
-          "statusText":"App status",
-          "transportId":"theTransportId"
-        }]
-      })";
-constexpr char kReceiverStatus2[] = R"({
-        "applications": [{
-          "appId": "ABCDEFGH",
-          "displayName": "Updated display name",
-          "namespaces": [
-            {"name": "urn:x-cast:com.google.cast.media"},
-            {"name": "urn:x-cast:com.google.foo"}
-          ],
-          "sessionId": "theSessionId",
-          "statusText":"App status",
-          "transportId":"theTransportId"
-        }]
-      })";
-constexpr char kReceiverStatus3[] = R"({
-        "applications": [{
-          "appId": "BBBBBBBB",
-          "displayName": "Another app",
-          "namespaces": [
-            {"name": "urn:x-cast:com.google.cast.media"},
-            {"name": "urn:x-cast:com.google.foo"}
-          ],
-          "sessionId": "theSessionId2",
-          "statusText":"App status",
-          "transportId":"theTransportId"
-        }]
-      })";
+          "statusText": "theAppStatus",
+          "transportId": "theTransportId",
+        }],
+      })");
+}
+
+using MockCastActivityRecordCallback =
+    base::RepeatingCallback<void(MockCastActivityRecord*)>;
+
 }  // namespace
 
-class ClientPresentationConnection
-    : public blink::mojom::PresentationConnection {
- public:
-  explicit ClientPresentationConnection(
-      mojom::RoutePresentationConnectionPtr connections)
-      : binding_(this, std::move(connections->connection_request)),
-        connection_(std::move(connections->connection_ptr)) {}
-
-  ~ClientPresentationConnection() override = default;
-
-  void SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessagePtr message) {
-    connection_->OnMessage(std::move(message));
-  }
-
-  MOCK_METHOD1(OnMessage, void(blink::mojom::PresentationConnectionMessagePtr));
-  MOCK_METHOD1(DidChangeState,
-               void(blink::mojom::PresentationConnectionState state));
-  MOCK_METHOD1(DidClose, void(blink::mojom::PresentationConnectionCloseReason));
-
-  mojo::Binding<blink::mojom::PresentationConnection> binding_;
-  blink::mojom::PresentationConnectionPtr connection_;
-  DISALLOW_COPY_AND_ASSIGN(ClientPresentationConnection);
-};
-
 // Test parameters are a boolean indicating whether the client connection should
 // be closed by a leave_session message, and the URL used to create the test
 // session.
-class CastActivityManagerTest
-    : public testing::TestWithParam<std::pair<bool, const char*>> {
+class CastActivityManagerTest : public testing::Test,
+                                public CastActivityRecordFactory {
  public:
   CastActivityManagerTest()
       : socket_service_(base::CreateSingleThreadTaskRunnerWithTraits(
@@ -122,6 +101,8 @@
   ~CastActivityManagerTest() override = default;
 
   void SetUp() override {
+    CastActivityManager::SetActitivyRecordFactoryForTest(this);
+
     router_binding_ = std::make_unique<mojo::Binding<mojom::MediaRouter>>(
         &mock_router_, mojo::MakeRequest(&router_ptr_));
 
@@ -132,12 +113,12 @@
         &media_sink_service_, session_tracker_.get(), &message_handler_,
         router_ptr_.get(),
         std::make_unique<DataDecoder>(connector_factory_.GetDefaultConnector()),
-        "hash-token");
+        "theHashToken");
 
     RunUntilIdle();
 
     // Make sure we get route updates.
-    manager_->AddRouteQuery(MediaSource::Id());
+    manager_->AddRouteQuery(route_query_);
   }
 
   void TearDown() override {
@@ -146,6 +127,27 @@
     RunUntilIdle();
 
     manager_.reset();
+    CastActivityManager::SetActitivyRecordFactoryForTest(nullptr);
+  }
+
+  std::unique_ptr<CastActivityRecordBase> MakeCastActivityRecord(
+      const MediaRoute& route,
+      const std::string& app_id) override {
+    auto activity = std::make_unique<MockCastActivityRecord>(route, app_id);
+    auto* activity_ptr = activity.get();
+    std::string route_id = route.media_route_id();
+    ON_CALL(*activity, SendStopSessionMessageToReceiver)
+        .WillByDefault(WithArg<2>([this, route_id](auto callback) {
+          result_callback_ = manager_->MakeResultCallbackForRoute(
+              route_id, std::move(callback));
+        }));
+    ON_CALL(*activity, SetOrUpdateSession)
+        .WillByDefault(WithArg<0>([activity_ptr](const auto& session) {
+          activity_ptr->set_session_id(session.session_id());
+        }));
+    activities_.push_back(activity_ptr);
+    activity_record_callback_.Run(activity_ptr);
+    return std::move(activity);
   }
 
   // Run any pending events and verify expectations associated with them.  This
@@ -164,16 +166,9 @@
       media_router::RouteRequestResult::ResultCode) {
     ASSERT_TRUE(route);
     route_ = std::make_unique<MediaRoute>(*route);
-    client_connection_ = std::make_unique<ClientPresentationConnection>(
-        std::move(presentation_connections));
-    // When client is connected, the receiver_action message will be sent.
-    EXPECT_CALL(
-        *client_connection_,
-        DidChangeState(blink::mojom::PresentationConnectionState::CONNECTED));
-    EXPECT_CALL(*client_connection_, OnMessage).RetiresOnSaturation();
   }
 
-  void LaunchSession(const char* sourceId = kSource1) {
+  void CallLaunchSession(const std::string& source_id = MakeSourceId(kAppId1)) {
     // MediaRouter is notified of new route.
     ExpectSingleRouteUpdate();
 
@@ -184,9 +179,17 @@
           launch_session_callback_ = std::move(callback);
         }));
 
-    auto source = CastMediaSource::FromMediaSourceId(sourceId);
+    auto source = CastMediaSource::FromMediaSourceId(source_id);
     ASSERT_TRUE(source);
 
+    activity_record_callback_ =
+        base::BindLambdaForTesting([this](MockCastActivityRecord* activity) {
+          // TODO(jrw): Check parameters.
+          EXPECT_CALL(*activity, AddClient);
+          EXPECT_CALL(*activity, SendMessageToClient).RetiresOnSaturation();
+          activity_record_callback_ = base::DoNothing();
+        });
+
     // Callback will be invoked synchronously.
     manager_->LaunchSession(
         *source, sink_, "presentationId", origin_, kTabId,
@@ -198,19 +201,24 @@
   }
 
   cast_channel::LaunchSessionResponse GetSuccessLaunchResponse() {
-    auto receiver_status = ParseJsonDeprecated(kReceiverStatus);
     cast_channel::LaunchSessionResponse response;
     response.result = cast_channel::LaunchSessionResponse::Result::kOk;
-    response.receiver_status = std::move(*receiver_status);
+    response.receiver_status = MakeReceiverStatus(kAppId1);
     return response;
   }
 
-  // Precondition: |LaunchSession()| must be called first.
-  void LaunchSessionResponseSuccess() {
+  void LaunchSession(const std::string& source_id = MakeSourceId(kAppId1)) {
+    CallLaunchSession(source_id);
+
     // 3 things will happen:
     // (1) SDK client receives new_session message.
     // (2) Virtual connection is created.
     // (3) Route list will be updated.
+
+    // TODO(jrw): Check more params.
+    EXPECT_CALL(*activities_[0], SendMessageToClient("theClientId", _));
+    EXPECT_CALL(*activities_[0], SetOrUpdateSession(_, sink_, _));
+
     EXPECT_CALL(message_handler_,
                 EnsureConnection(kChannelId, "theClientId", "theTransportId"));
 
@@ -219,170 +227,135 @@
         route_->media_sink_id(),
         CastSession::From(sink_, *response.receiver_status));
     std::move(launch_session_callback_).Run(std::move(response));
-    EXPECT_CALL(*client_connection_, OnMessage).RetiresOnSaturation();
     ExpectSingleRouteUpdate();
     RunUntilIdle();
   }
 
-  // Precondition: |LaunchSession()| must be called first.
-  void LaunchSessionResponseFailure() {
-    // 3 things will happen:
-    // (1) Route is removed
-    // (2) Issue will be sent.
-    // (3) The PresentationConnection associated with the route will be closed
-    //     with error.
-    cast_channel::LaunchSessionResponse response;
-    response.result = cast_channel::LaunchSessionResponse::Result::kError;
-    std::move(launch_session_callback_).Run(std::move(response));
-
-    EXPECT_CALL(mock_router_, OnIssue);
-    ExpectEmptyRouteUpdate();
-    EXPECT_CALL(
-        *client_connection_,
-        DidClose(
-            blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR));
-    RunUntilIdle();
+  void TerminateSession(bool expect_success) {
+    EXPECT_CALL(*activities_[0], SendStopSessionMessageToReceiver);
+    if (expect_success) {
+      ExpectEmptyRouteUpdate();
+    } else {
+      ExpectNoRouteUpdate();
+    }
+    manager_->TerminateSession(route_->media_route_id(),
+                               MakeTerminateRouteCallback(expect_success));
+    std::move(result_callback_)
+        .Run(expect_success ? cast_channel::Result::kOk
+                            : cast_channel::Result::kFailed);
   }
 
-  // Precondition: |LaunchSession()| must be called first.
-  void TerminateSession(cast_channel::Result result) {
-    cast_channel::ResultCallback stop_session_callback;
-
-    EXPECT_CALL(message_handler_, StopSession(kChannelId, "theSessionId",
-                                              base::Optional<std::string>(), _))
-        .WillOnce(WithArg<3>([&](auto callback) {
-          stop_session_callback = std::move(callback);
-        }));
-    manager_->TerminateSession(
-        route_->media_route_id(),
-        base::BindOnce(
-            result == cast_channel::Result::kOk
-                ? &CastActivityManagerTest::ExpectTerminateResultSuccess
-                : &CastActivityManagerTest::ExpectTerminateResultFailure,
-            base::Unretained(this)));
-    // Receiver action stop message is sent to SDK client.
-    EXPECT_CALL(*client_connection_, OnMessage);
-    RunUntilIdle();
-
-    std::move(stop_session_callback).Run(result);
-  }
-
-  // Precondition: |LaunchSession()| called, |LaunchSessionResponseSuccess()|
-  // not called.
   void TerminateNoSession() {
     // Stop session message not sent because session has not launched yet.
-    EXPECT_CALL(message_handler_, StopSession).Times(0);
-    manager_->TerminateSession(
-        route_->media_route_id(),
-        base::BindOnce(&CastActivityManagerTest::ExpectTerminateResultSuccess,
-                       base::Unretained(this)));
-    RunUntilIdle();
-  }
-
-  void ExpectTerminateResultSuccess(
-      const base::Optional<std::string>& error_text,
-      RouteRequestResult::ResultCode result_code) {
-    EXPECT_EQ(RouteRequestResult::OK, result_code);
-    ExpectEmptyRouteUpdate();
-    EXPECT_CALL(
-        *client_connection_,
-        DidChangeState(blink::mojom::PresentationConnectionState::TERMINATED));
-    RunUntilIdle();
-  }
-
-  void ExpectTerminateResultFailure(
-      const base::Optional<std::string>& error_text,
-      RouteRequestResult::ResultCode result_code) {
-    EXPECT_NE(RouteRequestResult::OK, result_code);
+    EXPECT_CALL(*activities_[0], SendStopSessionMessageToReceiver).Times(0);
     ExpectNoRouteUpdate();
-    RunUntilIdle();
+    manager_->TerminateSession(route_->media_route_id(),
+                               MakeTerminateRouteCallback(true));
+  }
+
+  mojom::MediaRouteProvider::TerminateRouteCallback MakeTerminateRouteCallback(
+      bool expect_success) {
+    return base::BindLambdaForTesting(
+        [expect_success](const base::Optional<std::string>& error_text,
+                         RouteRequestResult::ResultCode result_code) {
+          if (expect_success) {
+            EXPECT_FALSE(error_text.has_value());
+            EXPECT_EQ(RouteRequestResult::OK, result_code);
+          } else {
+            EXPECT_TRUE(error_text.has_value());
+            EXPECT_NE(RouteRequestResult::OK, result_code);
+          }
+        });
   }
 
   // Expect a call to OnRoutesUpdated() with a single route, which will
   // optionally be saved in the variable pointed to by |route_ptr|.
-  void ExpectSingleRouteUpdate(MediaRoute* route_ptr = nullptr) {
-    EXPECT_CALL(mock_router_, OnRoutesUpdated(MediaRouteProviderId::CAST,
-                                              Not(IsEmpty()), _, _))
-        .WillOnce(WithArg<1>([=](auto routes) {
-          EXPECT_EQ(1u, routes.size());
-          if (route_ptr) {
-            *route_ptr = routes[0];
-          }
-        }));
+  void ExpectSingleRouteUpdate() {
+    updated_route_ = base::nullopt;
+    EXPECT_CALL(mock_router_,
+                OnRoutesUpdated(MediaRouteProviderId::CAST, ElementsAre(_),
+                                route_query_, IsEmpty()))
+        .WillOnce(WithArg<1>(
+            [this](const auto& routes) { updated_route_ = routes[0]; }));
   }
 
   // Expect a call to OnRoutesUpdated() with no routes.
   void ExpectEmptyRouteUpdate() {
+    updated_route_ = base::nullopt;
     EXPECT_CALL(mock_router_,
-                OnRoutesUpdated(MediaRouteProviderId::CAST, IsEmpty(), _, _))
+                OnRoutesUpdated(MediaRouteProviderId::CAST, IsEmpty(),
+                                route_query_, IsEmpty()))
         .Times(1);
   }
 
   // Expect that OnRoutesUpdated() will not be called.
   void ExpectNoRouteUpdate() {
+    updated_route_ = base::nullopt;
     EXPECT_CALL(mock_router_, OnRoutesUpdated).Times(0);
   }
 
+  std::unique_ptr<CastSession> MakeSession(const std::string& app_id,
+                                           bool update_display_name = false) {
+    return CastSession::From(sink_,
+                             MakeReceiverStatus(app_id, update_display_name));
+  }
+
  protected:
   content::TestBrowserThreadBundle thread_bundle_;
   data_decoder::TestingJsonParser::ScopedFactoryOverride parser_override_;
   service_manager::TestConnectorFactory connector_factory_;
-
   MockMojoMediaRouter mock_router_;
   mojom::MediaRouterPtr router_ptr_;
   std::unique_ptr<mojo::Binding<mojom::MediaRouter>> router_binding_;
-
   cast_channel::MockCastSocketService socket_service_;
   cast_channel::MockCastSocket socket_;
   cast_channel::MockCastMessageHandler message_handler_;
-
   MediaSinkInternal sink_ = CreateCastSink(kChannelId);
-  std::unique_ptr<MediaRoute> route_;
-  std::unique_ptr<ClientPresentationConnection> client_connection_;
+  std::unique_ptr<MediaRoute> route_;  // TODO(jrw): Is this needed?
   cast_channel::LaunchSessionCallback launch_session_callback_;
-
   TestMediaSinkService media_sink_service_;
   MockCastAppDiscoveryService app_discovery_service_;
   std::unique_ptr<CastActivityManager> manager_;
-
   std::unique_ptr<CastSessionTracker> session_tracker_;
-
+  std::vector<MockCastActivityRecord*> activities_;
+  MockCastActivityRecordCallback activity_record_callback_ = base::DoNothing();
   const url::Origin origin_ = url::Origin::Create(GURL(kOrigin));
+  const MediaSource::Id route_query_ = "theRouteQuery";
+  base::Optional<MediaRoute> updated_route_;
+  cast_channel::ResultCallback result_callback_;
 };
 
 TEST_F(CastActivityManagerTest, LaunchSession) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
 }
 
 TEST_F(CastActivityManagerTest, LaunchSessionFails) {
-  LaunchSession();
-  LaunchSessionResponseFailure();
+  // 3 things will happen:
+  // (1) Route is removed
+  // (2) Issue will be sent.
+  // (3) The PresentationConnection associated with the route will be closed
+  //     with error.
+
+  CallLaunchSession();
+
+  EXPECT_CALL(
+      *activities_[0],
+      ClosePresentationConnections(
+          blink::mojom::PresentationConnectionCloseReason::CONNECTION_ERROR));
+
+  cast_channel::LaunchSessionResponse response;
+  response.result = cast_channel::LaunchSessionResponse::Result::kError;
+  std::move(launch_session_callback_).Run(std::move(response));
+
+  EXPECT_CALL(mock_router_, OnIssue);
+  ExpectEmptyRouteUpdate();
+  RunUntilIdle();
 }
 
 TEST_F(CastActivityManagerTest, LaunchSessionTerminatesExistingSessionOnSink) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
 
-  // Receiver action stop message is sent to SDK client.
-  EXPECT_CALL(*client_connection_, OnMessage);
-
-  // Existing session will be terminated.
-  cast_channel::ResultCallback stop_session_callback;
-  EXPECT_CALL(message_handler_, StopSession(kChannelId, "theSessionId",
-                                            base::Optional<std::string>(), _))
-      .WillOnce(WithArg<3>(
-          [&](auto callback) { stop_session_callback = std::move(callback); }));
-
-  // Launch a new session on the same sink.
-  auto source = CastMediaSource::FromMediaSourceId(kSource2);
-  ASSERT_TRUE(source);
-  manager_->LaunchSession(
-      *source, sink_, "presentationId2", origin_, kTabId, /*incognito*/
-      false,
-      base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionSuccess,
-                     base::Unretained(this)));
-  RunUntilIdle();
+  EXPECT_CALL(*activities_[0], SendStopSessionMessageToReceiver);
 
   {
     testing::InSequence dummy;
@@ -396,24 +369,27 @@
     ExpectSingleRouteUpdate();
   }
 
-  // A launch session request is sent to the sink.
+  // Launch a new session on the same sink.
+  auto source = CastMediaSource::FromMediaSourceId(MakeSourceId(kAppId2));
+  manager_->LaunchSession(
+      // TODO(jrw): Verify that presentation ID is used correctly.
+      *source, sink_, "presentationId2", origin_, kTabId, /*incognito*/
+      false,
+      base::BindOnce(&CastActivityManagerTest::ExpectLaunchSessionSuccess,
+                     base::Unretained(this)));
+
   EXPECT_CALL(message_handler_,
               LaunchSession(kChannelId, "BBBBBBBB", kDefaultLaunchTimeout, _));
-
-  std::move(stop_session_callback).Run(cast_channel::Result::kOk);
+  std::move(result_callback_).Run(cast_channel::Result::kOk);
 }
 
 TEST_F(CastActivityManagerTest, AddRemoveNonLocalActivity) {
-  auto session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
-  ASSERT_TRUE(session);
-
-  MediaRoute route;
-  ExpectSingleRouteUpdate(&route);
+  auto session = MakeSession(kAppId1);
+  ExpectSingleRouteUpdate();
   manager_->OnSessionAddedOrUpdated(sink_, *session);
   RunUntilIdle();
-
-  EXPECT_FALSE(route.is_local());
+  ASSERT_TRUE(updated_route_);
+  EXPECT_FALSE(updated_route_->is_local());
 
   ExpectEmptyRouteUpdate();
   manager_->OnSessionRemoved(sink_);
@@ -421,340 +397,80 @@
 
 TEST_F(CastActivityManagerTest, UpdateNewlyCreatedSession) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
 
-  auto session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
-  ASSERT_TRUE(session);
-
-  MediaRoute route;
-  ExpectSingleRouteUpdate(&route);
-  EXPECT_CALL(*client_connection_, OnMessage);
-
+  EXPECT_CALL(*activities_[0], SetOrUpdateSession(_, sink_, _));
+  auto session = MakeSession(kAppId1);
+  ExpectSingleRouteUpdate();
   manager_->OnSessionAddedOrUpdated(sink_, *session);
   RunUntilIdle();
-
-  EXPECT_TRUE(route.is_local());
-  EXPECT_EQ(route.description(), session->GetRouteDescription());
+  ASSERT_TRUE(updated_route_);
+  EXPECT_TRUE(updated_route_->is_local());
 }
 
-TEST_F(CastActivityManagerTest, UpdateExistingSession) {
-  // Create and add the session to be updated, and verify it was added.
-  auto session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
-  ASSERT_TRUE(session);
-  MediaRoute route;
-  ExpectSingleRouteUpdate(&route);
+TEST_F(CastActivityManagerTest, OnSessionAddedOrUpdated) {
+  LaunchSession();
+  auto session = MakeSession(kAppId1);
+  ExpectSingleRouteUpdate();
+  EXPECT_CALL(*activities_[0], SetOrUpdateSession(_, _, "theHashToken"));
   manager_->OnSessionAddedOrUpdated(sink_, *session);
-  RunUntilIdle();
-  EXPECT_EQ(route.description(), session->GetRouteDescription());
-  auto old_route_id = route.media_route_id();
-
-  // Description change should be reflect in route update.
-  auto updated_session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus2));
-  ASSERT_TRUE(updated_session);
-
-  ExpectSingleRouteUpdate(&route);
-
-  manager_->OnSessionAddedOrUpdated(sink_, *updated_session);
-  RunUntilIdle();
-
-  EXPECT_EQ(route.description(), updated_session->GetRouteDescription());
-  EXPECT_EQ(old_route_id, route.media_route_id());
-}
-
-TEST_F(CastActivityManagerTest, ReplaceExistingSession) {
-  // Create and add the session to be replaced, and verify it was added.
-  auto session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus));
-  ASSERT_TRUE(session);
-  MediaRoute route;
-  ExpectSingleRouteUpdate(&route);
-  manager_->OnSessionAddedOrUpdated(sink_, *session);
-  RunUntilIdle();
-  auto old_route_id = route.media_route_id();
-  EXPECT_EQ(route.description(), session->GetRouteDescription());
-
-  // Different session.
-  auto new_session =
-      CastSession::From(sink_, *ParseJsonDeprecated(kReceiverStatus3));
-  ASSERT_TRUE(new_session);
-
-  ExpectSingleRouteUpdate(&route);
-  manager_->OnSessionAddedOrUpdated(sink_, *new_session);
-  RunUntilIdle();
-
-  EXPECT_EQ(route.description(), new_session->GetRouteDescription());
-  EXPECT_NE(old_route_id, route.media_route_id());
 }
 
 TEST_F(CastActivityManagerTest, TerminateSession) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
-  TerminateSession(cast_channel::Result::kOk);
+  TerminateSession(true);
 }
 
 TEST_F(CastActivityManagerTest, TerminateSessionFails) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
-  TerminateSession(cast_channel::Result::kFailed);
+  TerminateSession(false);
 }
 
 TEST_F(CastActivityManagerTest, TerminateSessionBeforeLaunchResponse) {
-  LaunchSession();
+  CallLaunchSession();
   TerminateNoSession();
-
-  // Route already terminated, so no-op when handling launch response.
+  ExpectEmptyRouteUpdate();
   std::move(launch_session_callback_).Run(GetSuccessLaunchResponse());
-  ExpectNoRouteUpdate();
 }
 
 TEST_F(CastActivityManagerTest, AppMessageFromReceiver) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
+
+  // TODO(jrw): Check message param.
+  EXPECT_CALL(*activities_[0], SendMessageToClient("theClientId", _));
 
   // Destination ID matches client ID.
   cast_channel::CastMessage message = cast_channel::CreateCastMessage(
       "urn:x-cast:com.google.foo", base::Value(base::Value::Type::DICTIONARY),
       "sourceId", "theClientId");
-  message_handler_.OnMessage(socket_, message);
-  EXPECT_CALL(*client_connection_, OnMessage);
+  manager_->OnAppMessage(kChannelId, message);
 }
 
 TEST_F(CastActivityManagerTest, AppMessageFromReceiverAllDestinations) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
+
+  activities_[0]->AddFakeClient("fakeClient1");
+  activities_[0]->AddFakeClient("fakeClient2");
+
+  // TODO(jrw): Check message params.
+  EXPECT_CALL(*activities_[0], SendMessageToClient("fakeClient1", _));
+  EXPECT_CALL(*activities_[0], SendMessageToClient("fakeClient2", _));
 
   // Matches all destinations.
   cast_channel::CastMessage message = cast_channel::CreateCastMessage(
       "urn:x-cast:com.google.foo", base::Value(base::Value::Type::DICTIONARY),
       "sourceId", "*");
-  message_handler_.OnMessage(socket_, message);
-  EXPECT_CALL(*client_connection_, OnMessage);
-}
-
-TEST_F(CastActivityManagerTest, AppMessageFromReceiverUnknownDestination) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  // Destination ID does not match client ID.
-  cast_channel::CastMessage message = cast_channel::CreateCastMessage(
-      "urn:x-cast:com.google.foo", base::Value(base::Value::Type::DICTIONARY),
-      "sourceId", "99999");
-  message_handler_.OnMessage(socket_, message);
-  EXPECT_CALL(*client_connection_, OnMessage).Times(0);
-}
-
-TEST_F(CastActivityManagerTest, AppMessageFromClient) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  EXPECT_CALL(message_handler_, SendAppMessage(kChannelId, _))
-      .WillOnce(Return(cast_channel::Result::kOk));
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "app_message",
-        "clientId": "theClientId",
-        "message": {
-          "namespaceName": "urn:x-cast:com.google.foo",
-          "sessionId": "theSessionId",
-          "message": {}
-        },
-        "sequenceNumber": 123
-      })"));
-
-  // An ACK message is sent back to client.
-  EXPECT_CALL(*client_connection_, OnMessage);
-}
-
-TEST_F(CastActivityManagerTest, AppMessageFromClientInvalidNamespace) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  // Message namespace not in set of allowed namespaces.
-  EXPECT_CALL(message_handler_, SendAppMessage(kChannelId, _)).Times(0);
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "app_message",
-        "clientId": "theClientId",
-        "message": {
-          "namespaceName": "someOtherNamespace",
-          "sessionId": "theSessionId",
-          "message": {}
-        }
-      })"));
+  manager_->OnAppMessage(kChannelId, message);
 }
 
 TEST_F(CastActivityManagerTest, OnMediaStatusUpdated) {
   LaunchSession();
-  LaunchSessionResponseSuccess();
 
-  EXPECT_CALL(*client_connection_, OnMessage(IsCastMessage(R"({
-    "clientId": "theClientId",
-    "message": {"foo": "bar"},
-    "timeoutMillis": 0,
-    "type": "v2_message"
-  })")));
-  manager_->OnMediaStatusUpdated(
-      sink_, *ParseJsonDeprecated(R"({"foo": "bar"})"), 345);
+  const char status[] = R"({"foo": "bar"})";
+  base::Optional<int> request_id(345);
+
+  EXPECT_CALL(*activities_[0],
+              SendMediaStatusToClients(IsJson(status), request_id));
+  manager_->OnMediaStatusUpdated(sink_, ParseJson(status), request_id);
 }
 
-TEST_F(CastActivityManagerTest, OnMediaStatusUpdatedWithPendingRequest) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  EXPECT_CALL(message_handler_, SendMediaRequest).WillOnce(Return(345));
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "MEDIA_GET_STATUS"
-        },
-        "sequenceNumber": 123
-      })"));
-  RunUntilIdle();
-
-  // Same as in OnMediaStatusUpdated, except there is a sequenceNumber field.
-  EXPECT_CALL(*client_connection_, OnMessage(IsCastMessage(R"({
-    "clientId": "theClientId",
-    "message": {"foo": "bar"},
-    "sequenceNumber": 123,
-    "timeoutMillis": 0,
-    "type": "v2_message"
-  })")));
-  manager_->OnMediaStatusUpdated(
-      sink_, *ParseJsonDeprecated(R"({"foo": "bar"})"), 345);
-}
-
-TEST_F(CastActivityManagerTest, SendVolumeCommandToReceiver) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  // Message created by CastActivityRecord::SendVolumeCommandToReceiver().
-  std::string expected_message = R"({
-    "sessionId": "theSessionId",
-    "type": "SET_VOLUME"
-  })";
-  EXPECT_CALL(message_handler_,
-              SendSetVolumeRequest(kChannelId, IsJson(expected_message),
-                                   "theClientId", _))
-      .WillOnce(WithArg<3>([&](auto callback) {
-        // Check message created by CastSessionClient::SendResultResponse().
-        EXPECT_CALL(*client_connection_, OnMessage(IsCastMessage(R"({
-                    "clientId": "theClientId",
-                    "message": null,
-                    "sequenceNumber": 123,
-                    "timeoutMillis": 0,
-                    "type": "v2_message"
-                  })")));
-        std::move(callback).Run(cast_channel::Result::kOk);
-        return cast_channel::Result::kOk;
-      }));
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "sequenceNumber": 123,
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "SET_VOLUME"
-        }
-      })"));
-}
-
-TEST_F(CastActivityManagerTest, SendMediaRequestToReceiver) {
-  LaunchSession();
-  LaunchSessionResponseSuccess();
-
-  std::string expected_message = R"({
-    "sessionId": "theSessionId",
-    "type": "MEDIA_GET_STATUS"
-  })";
-  EXPECT_CALL(message_handler_,
-              SendMediaRequest(kChannelId, IsJson(expected_message),
-                               "theClientId", "theTransportId"));
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "MEDIA_GET_STATUS"
-        },
-        "sequenceNumber": 123
-      })"));
-}
-
-TEST_F(CastActivityManagerTest, CanJoinSession) {
-  // TODO(crbug/943291): Write test for CastActivityManager::CanJoinSession.
-}
-
-TEST_F(CastActivityManagerTest, FindActivityForSessionJoin) {
-  // TODO(crbug/943291): Write test for
-  // CastActivityManager::FindActivityForSessionJoin.
-}
-
-TEST_F(CastActivityManagerTest, FindActivityForAutoJoin) {
-  // TODO(crbug/943291): Write test for
-  // CastActivityManager::FindActivityForAutoJoin.
-}
-
-TEST_F(CastActivityManagerTest, JoinSession) {
-  // TODO(crbug/943291): Write test for CastActivityManager::JoinSession.
-}
-
-TEST_P(CastActivityManagerTest, HandleLeaveSession) {
-  bool should_close;
-  const char* url;
-  std::tie(should_close, url) = GetParam();
-
-  LaunchSession(url);
-  LaunchSessionResponseSuccess();
-
-  // Called via CastSessionClient::SendMessageToClient.
-  EXPECT_CALL(*client_connection_, OnMessage).WillOnce([](auto msg) {
-    // Verify that an acknowlegement message was sent.
-    ASSERT_TRUE(msg->is_message());
-    EXPECT_THAT(msg->get_message(), IsJson(R"({
-          "clientId": "theClientId",
-          "message": null,
-          "timeoutMillis": 0,
-          "type": "leave_session",
-        })"));
-  });
-
-  if (should_close) {
-    // Called via CastSessionClient::CloseConnection via
-    // CastActivityRecord::HandleLeaveSession.
-    EXPECT_CALL(
-        *client_connection_,
-        DidClose(blink::mojom::PresentationConnectionCloseReason::CLOSED));
-  }
-
-  client_connection_->SendMessageToMediaRouter(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "leave_session",
-        "clientId": "theClientId",
-        "message": {}
-      })"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    Urls,
-    CastActivityManagerTest,
-    testing::Values(
-        std::make_pair(true,
-                       "cast:ABCDEFGH?clientId=theClientId&autoJoinPolicy=tab_"
-                       "and_origin_scoped"),
-        std::make_pair(
-            true,
-            "cast:ABCDEFGH?clientId=theClientId&autoJoinPolicy=origin_scoped"),
-        std::make_pair(
-            false,
-            "cast:ABCDEFGH?clientId=theClientId&autoJoinPolicy=page_scoped"),
-        std::make_pair(false, "cast:ABCDEFGH?clientId=theClientId")));
-
 }  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record.cc b/chrome/browser/media/router/providers/cast/cast_activity_record.cc
index 3016f316..1489b7e 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_record.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_record.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #include "chrome/common/media_router/mojo/media_router.mojom.h"
 #include "chrome/browser/media/router/providers/cast/cast_activity_record.h"
 
 #include <memory>
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc b/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc
index 1875301..1184738b 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/media/router/providers/cast/cast_activity_record.h"
 
 #include <memory>
+#include <sstream>
 #include <string>
 #include <tuple>
 #include <utility>
@@ -80,6 +81,8 @@
                void(blink::mojom::PresentationConnectionCloseReason reason));
 };
 
+using NewClientCallback = base::RepeatingCallback<void(MockCastSessionClient&)>;
+
 class MockCastActivityManager : public CastActivityManagerBase {
  public:
   MOCK_METHOD2(MakeResultCallbackForRoute,
@@ -91,7 +94,7 @@
 }  // namespace
 
 class CastActivityRecordTest : public testing::Test,
-                               public CastSessionClientFactoryForTest {
+                               CastSessionClientFactoryForTest {
  public:
   CastActivityRecordTest() {}
 
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
index 189883d..4af4556 100644
--- a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
+++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
@@ -16,11 +16,12 @@
 #include "chrome/common/media_router/providers/cast/cast_media_source.h"
 #include "components/cast_channel/cast_socket.h"
 #include "components/cast_channel/enum_table.h"
+#include "components/cast_channel/proto/cast_channel.pb.h"
 #include "net/base/escape.h"
 
 namespace cast_util {
 
-using media_router::CastInternalMessage;
+using namespace media_router;
 
 template <>
 const EnumTable<CastInternalMessage::Type>
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.cc b/chrome/browser/media/router/providers/cast/cast_session_client.cc
index dcf7f3ce..d30d336 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client.cc
+++ b/chrome/browser/media/router/providers/cast/cast_session_client.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #include "chrome/common/media_router/mojo/media_router.mojom.h"
 #include "chrome/browser/media/router/providers/cast/cast_session_client.h"
 
 #include "base/bind.h"
-#include "base/logging.h"
 #include "chrome/browser/media/router/data_decoder_util.h"
 #include "chrome/browser/media/router/providers/cast/cast_activity_record.h"
 #include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
@@ -23,9 +23,9 @@
 
 void ReportClientMessageParseError(const MediaRoute::Id& route_id,
                                    const std::string& error) {
-  // TODO(crbug.com/905002): Record UMA metric for parse result.
-  LOG(ERROR) << "Failed to parse Cast client message for " << route_id << ": "
-             << error;
+  // TODO(crbug.com/808720): Record UMA metric for parse result.
+  DVLOG(2) << "Failed to parse Cast client message for " << route_id << ": "
+           << error;
 }
 
 }  // namespace
@@ -42,7 +42,7 @@
                                      int tab_id,
                                      AutoJoinPolicy auto_join_policy,
                                      DataDecoder* data_decoder,
-                                     CastActivityRecordBase* activity)
+                                     CastActivityRecord* activity)
     : CastSessionClientBase(client_id, origin, tab_id),
       auto_join_policy_(auto_join_policy),
       data_decoder_(data_decoder),
@@ -142,6 +142,7 @@
   if (!cast_message) {
     ReportClientMessageParseError(activity_->route().media_route_id(),
                                   "Not a Cast message");
+    DLOG(ERROR) << "Received non-Cast message from client";
     return;
   }
 
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.h b/chrome/browser/media/router/providers/cast/cast_session_client.h
index 43c115f6..9b83676 100644
--- a/chrome/browser/media/router/providers/cast/cast_session_client.h
+++ b/chrome/browser/media/router/providers/cast/cast_session_client.h
@@ -23,7 +23,7 @@
 
 namespace media_router {
 
-class CastActivityRecordBase;
+class CastActivityRecord;
 class DataDecoder;
 
 // TODO(jrw): Rename
@@ -117,7 +117,7 @@
                     int tab_id,
                     AutoJoinPolicy auto_join_policy,
                     DataDecoder* data_decoder,
-                    CastActivityRecordBase* activity);
+                    CastActivityRecord* activity);
   ~CastSessionClient() override;
 
   // CastSessionClientBase implementation
@@ -161,7 +161,7 @@
   const AutoJoinPolicy auto_join_policy_;
 
   DataDecoder* const data_decoder_;
-  CastActivityRecordBase* const activity_;
+  CastActivityRecord* const activity_;
 
   // The maximum number of pending media requests, used to prevent memory leaks.
   // Normally the number of pending requests should be fairly small, but each
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc b/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc
deleted file mode 100644
index 73828e4..0000000
--- a/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc
+++ /dev/null
@@ -1,252 +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 "chrome/browser/media/router/providers/cast/cast_session_client.h"
-
-// #include <iostream>
-#include <memory>
-#include <tuple>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/run_loop.h"
-#include "base/task/post_task.h"
-#include "base/test/mock_log.h"
-#include "base/test/values_test_util.h"
-#include "chrome/browser/media/router/data_decoder_util.h"
-#include "chrome/browser/media/router/providers/cast/cast_activity_manager.h"
-#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
-#include "chrome/browser/media/router/providers/cast/mock_cast_activity_record.h"
-#include "chrome/browser/media/router/providers/cast/test_util.h"
-#include "chrome/browser/media/router/providers/common/buffered_message_sender.h"
-#include "chrome/browser/media/router/test/mock_mojo_media_router.h"
-#include "chrome/browser/media/router/test/test_helper.h"
-#include "chrome/common/media_router/test/test_helper.h"
-#include "components/cast_channel/cast_test_util.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-// #include "services/data_decoder/data_decoder_service.h"
-#include "services/data_decoder/public/cpp/testing_json_parser.h"
-// #include "services/data_decoder/public/mojom/constants.mojom.h"
-#include "services/service_manager/public/cpp/test/test_connector_factory.h"
-// #include "testing/gmock/include/gmock/gmock.h"
-// #include "testing/gtest/include/gtest/gtest.h"
-
-using base::test::IsJson;
-using base::test::ParseJson;
-using testing::_;
-using testing::AllOf;
-using testing::AnyNumber;
-using testing::HasSubstr;
-using testing::IsEmpty;
-using testing::Not;
-using testing::Return;
-using testing::WithArg;
-
-namespace media_router {
-
-namespace {
-constexpr int kTabId = 213;
-
-class MockPresentationConnection : public blink::mojom::PresentationConnection {
- public:
-  explicit MockPresentationConnection(
-      mojom::RoutePresentationConnectionPtr connections)
-      : binding_(this, std::move(connections->connection_request)) {}
-
-  ~MockPresentationConnection() override = default;
-
-  MOCK_METHOD1(OnMessage, void(blink::mojom::PresentationConnectionMessagePtr));
-  MOCK_METHOD1(DidChangeState,
-               void(blink::mojom::PresentationConnectionState state));
-  MOCK_METHOD1(DidClose, void(blink::mojom::PresentationConnectionCloseReason));
-
-  // NOTE: This member doesn't look like it's used for anything, but it needs to
-  // exist in order for Mojo magic to work correctly.
-  mojo::Binding<blink::mojom::PresentationConnection> binding_;
-
-  DISALLOW_COPY_AND_ASSIGN(MockPresentationConnection);
-};
-
-}  // namespace
-
-class CastSessionClientTest : public testing::Test {
- public:
-  CastSessionClientTest() { activity_.set_session_id("theSessionId"); }
-
-  ~CastSessionClientTest() override { RunUntilIdle(); }
-
- protected:
-  void RunUntilIdle() { thread_bundle_.RunUntilIdle(); }
-
-  content::TestBrowserThreadBundle thread_bundle_;
-  data_decoder::TestingJsonParser::ScopedFactoryOverride parser_override_;
-  service_manager::TestConnectorFactory connector_factory_;
-  cast_channel::MockCastSocketService socket_service_{
-      base::CreateSingleThreadTaskRunnerWithTraits(
-          {content::BrowserThread::UI})};
-  cast_channel::MockCastMessageHandler message_handler_{&socket_service_};
-  DataDecoder decoder_{connector_factory_.GetDefaultConnector()};
-  url::Origin origin_;
-  MediaRoute route_;
-  MockCastActivityRecord activity_{route_, "theAppId"};
-  std::unique_ptr<CastSessionClient> client_ =
-      std::make_unique<CastSessionClient>("theClientId",
-                                          origin_,
-                                          kTabId,
-                                          AutoJoinPolicy::kPageScoped,
-                                          &decoder_,
-                                          &activity_);
-  std::unique_ptr<MockPresentationConnection> mock_connection_ =
-      std::make_unique<MockPresentationConnection>(client_->Init());
-  base::test::MockLog log_;
-};
-
-TEST_F(CastSessionClientTest, OnInvalidJson) {
-  // TODO(crbug.com/905002): Check UMA calls instead of logging (here and
-  // below).
-  EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _,
-                        HasSubstr("Failed to parse Cast client message")))
-      .WillOnce(Return(true));  // suppress logging
-
-  log_.StartCapturingLogs();
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage("invalid js"));
-}
-
-TEST_F(CastSessionClientTest, OnInvalidMessage) {
-  EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _,
-                        AllOf(HasSubstr("Failed to parse Cast client message"),
-                              HasSubstr("Not a Cast message"))))
-      .WillOnce(Return(true));  // suppress logging
-
-  log_.StartCapturingLogs();
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage("{}"));
-}
-
-TEST_F(CastSessionClientTest, OnMessageWrongClientId) {
-  EXPECT_CALL(
-      log_, Log(logging::LOG_ERROR, _, _, _,
-                AllOf(HasSubstr("Client ID mismatch"), HasSubstr("theClientId"),
-                      HasSubstr("theWrongClientId"))))
-      .WillOnce(Return(true));  // suppress logging
-
-  log_.StartCapturingLogs();
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theWrongClientId",
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "MEDIA_GET_STATUS"
-        }
-      })"));
-}
-
-TEST_F(CastSessionClientTest, OnMessageWrongSessionId) {
-  EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _,
-                        AllOf(HasSubstr("Session ID mismatch"),
-                              HasSubstr("theSessionId"),
-                              HasSubstr("theWrongSessionId"))))
-      .WillOnce(Return(true));  // suppress logging
-
-  log_.StartCapturingLogs();
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "message": {
-          "sessionId": "theWrongSessionId",
-          "type": "MEDIA_GET_STATUS"
-        }
-      })"));
-}
-
-TEST_F(CastSessionClientTest, AppMessageFromClient) {
-  EXPECT_CALL(activity_, SendAppMessageToReceiver)
-      .WillOnce(Return(cast_channel::Result::kOk));
-
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "app_message",
-        "clientId": "theClientId",
-        "message": {
-          "namespaceName": "urn:x-cast:com.google.foo",
-          "sessionId": "theSessionId",
-          "message": {}
-        },
-        "sequenceNumber": 123
-      })"));
-}
-
-TEST_F(CastSessionClientTest, OnMediaStatusUpdatedWithPendingRequest) {
-  EXPECT_CALL(activity_, SendMediaRequestToReceiver(IsCastInternalMessage(R"({
-    "type": "v2_message",
-    "clientId": "theClientId",
-    "sequenceNumber": 123,
-    "message": {
-       "sessionId": "theSessionId",
-       "type": "MEDIA_GET_STATUS"
-    }
-  })")))
-      .WillOnce(Return(345));
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "MEDIA_GET_STATUS"
-        },
-        "sequenceNumber": 123
-      })"));
-  RunUntilIdle();
-
-  EXPECT_CALL(*mock_connection_, OnMessage(IsCastMessage(R"({
-    "clientId": "theClientId",
-    "message": {"foo": "bar"},
-    "timeoutMillis": 0,
-    "type": "v2_message"
-  })")));
-  client_->SendMediaStatusToClient(ParseJson(R"({"foo": "bar"})"), 123);
-}
-
-TEST_F(CastSessionClientTest, SendSetVolumeCommandToReceiver) {
-  EXPECT_CALL(activity_,
-              SendSetVolumeRequestToReceiver(IsCastInternalMessage(R"({
-    "type": "v2_message",
-    "clientId": "theClientId",
-    "sequenceNumber": 123,
-    "message": {
-       "sessionId": "theSessionId",
-       "type": "SET_VOLUME"
-    }
-  })"),
-                                             _))
-      .WillOnce(WithArg<1>([](auto callback) {
-        std::move(callback).Run(cast_channel::Result::kOk);
-      }));
-  EXPECT_CALL(*mock_connection_, OnMessage(IsCastMessage(R"({
-    "clientId": "theClientId",
-    "message": null,
-    "sequenceNumber": 123,
-    "timeoutMillis": 0,
-    "type": "v2_message"
-  })")));
-
-  client_->OnMessage(
-      blink::mojom::PresentationConnectionMessage::NewMessage(R"({
-        "type": "v2_message",
-        "clientId": "theClientId",
-        "sequenceNumber": 123,
-        "message": {
-          "sessionId": "theSessionId",
-          "type": "SET_VOLUME"
-        }
-      })"));
-}
-
-}  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h b/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h
index a72a33f..125c0ca 100644
--- a/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h
+++ b/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h
@@ -5,10 +5,7 @@
 #ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_MOCK_CAST_ACTIVITY_RECORD_H_
 #define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_MOCK_CAST_ACTIVITY_RECORD_H_
 
-#include <string>
-
 #include "chrome/browser/media/router/providers/cast/cast_activity_record.h"
-#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
 #include "chrome/browser/media/router/providers/cast/cast_session_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/chrome/browser/media/router/providers/cast/test_util.cc b/chrome/browser/media/router/providers/cast/test_util.cc
deleted file mode 100644
index 6c75cdc..0000000
--- a/chrome/browser/media/router/providers/cast/test_util.cc
+++ /dev/null
@@ -1,42 +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 "chrome/browser/media/router/providers/cast/test_util.h"
-
-#include "components/cast_channel/enum_table.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media_router {
-
-std::ostream& operator<<(std::ostream& out, CastInternalMessage::Type type) {
-  return out << cast_util::EnumToString(type).value_or("<invalid>");
-}
-
-// TODO(jrw): Why won't gtest call this operator?
-std::ostream& operator<<(std::ostream& out,
-                         const CastInternalMessage& message) {
-  out << "{type=" << message.type() << ", client_id=" << message.client_id();
-  if (message.sequence_number()) {
-    out << ", sequence_number=" << *message.sequence_number();
-  }
-  if (message.has_session_id()) {
-    out << ", session_id=" << message.session_id();
-  }
-
-  switch (message.type()) {
-    case CastInternalMessage::Type::kAppMessage:
-      out << ", app_message_namespace=" << message.app_message_namespace()
-          << ", app_message_body=" << message.app_message_body();
-      break;
-    case CastInternalMessage::Type::kV2Message:
-      out << ", v2_message_type=" << message.v2_message_type()
-          << ", v2_message_body=" << message.v2_message_body();
-      break;
-    default:
-      break;
-  }
-  return out << "}";
-}
-
-}  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/test_util.h b/chrome/browser/media/router/providers/cast/test_util.h
deleted file mode 100644
index b50f4d15..0000000
--- a/chrome/browser/media/router/providers/cast/test_util.h
+++ /dev/null
@@ -1,46 +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 CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_
-#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_
-
-#include <iosfwd>
-
-#include "base/test/values_test_util.h"
-#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace media_router {
-
-std::ostream& operator<<(std::ostream&, CastInternalMessage::Type);
-std::ostream& operator<<(std::ostream&, const CastInternalMessage&);
-
-// Matcher for CastInternalMessage arguments.
-MATCHER_P(IsCastInternalMessage, json, "") {
-  auto message = CastInternalMessage::From(base::test::ParseJson(json));
-  DCHECK(message);
-  if (arg.type() != message->type() ||
-      arg.client_id() != message->client_id() ||
-      arg.sequence_number() != message->sequence_number())
-    return false;
-
-  if (arg.has_session_id() && arg.session_id() != message->session_id())
-    return false;
-
-  switch (arg.type()) {
-    case CastInternalMessage::Type::kAppMessage:
-      return arg.app_message_namespace() == message->app_message_namespace() &&
-             arg.app_message_body() == message->app_message_body();
-    case CastInternalMessage::Type::kV2Message:
-      return arg.v2_message_type() == message->v2_message_type() &&
-             testing::Matches(base::test::IsJson(arg.v2_message_body()))(
-                 message->v2_message_body());
-    default:
-      return true;
-  }
-}
-
-}  // namespace media_router
-
-#endif  // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_
diff --git a/chrome/browser/media/router/test/test_helper.h b/chrome/browser/media/router/test/test_helper.h
index 677edd0..7873af0 100644
--- a/chrome/browser/media/router/test/test_helper.h
+++ b/chrome/browser/media/router/test/test_helper.h
@@ -1,4 +1,3 @@
-
 // Copyright 2015 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -6,15 +5,20 @@
 #ifndef CHROME_BROWSER_MEDIA_ROUTER_TEST_TEST_HELPER_H_
 #define CHROME_BROWSER_MEDIA_ROUTER_TEST_TEST_HELPER_H_
 
+#include <stddef.h>
+#include <stdint.h>
+
 #include <string>
 #include <vector>
 
 #include "base/macros.h"
 #include "base/test/values_test_util.h"
+#include "build/build_config.h"
 #include "chrome/browser/media/router/issue_manager.h"
 #include "chrome/browser/media/router/issues_observer.h"
 #include "chrome/browser/media/router/media_routes_observer.h"
 #include "chrome/browser/media/router/media_sinks_observer.h"
+#include "content/public/browser/presentation_service_delegate.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/mojom/presentation/presentation.mojom.h"
 
@@ -226,8 +230,6 @@
 
 #endif  // !defined(OS_ANDROID)
 
-// Matcher for blink::mojom::PresentationConnectionMessagePtr arguments.
-// TODO(jrw): Rename to something like IsPresentationConnectionMessage.
 MATCHER_P(IsCastMessage, json, "") {
   return arg->is_message() && base::test::IsJsonMatcher(json).MatchAndExplain(
                                   arg->get_message(), result_listener);
diff --git a/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc b/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
index b1e69873b..abcc65b9 100644
--- a/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
+++ b/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
@@ -467,7 +467,7 @@
   EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(origin));
 
   std::string url = web_contents->GetLastCommittedURL().spec();
-  ASSERT_EQ("chrome://settings/content/notifications", url);
+  ASSERT_EQ(content::GetWebUIURLString("settings/content/notifications"), url);
 }
 #endif
 
diff --git a/chrome/browser/performance_manager/BUILD.gn b/chrome/browser/performance_manager/BUILD.gn
new file mode 100644
index 0000000..de17cf02
--- /dev/null
+++ b/chrome/browser/performance_manager/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//chrome/common/features.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [
+    "webui_graph_dump.mojom",
+  ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
+}
diff --git a/chrome/browser/performance_manager/OWNERS b/chrome/browser/performance_manager/OWNERS
index 24d881e8..969f30f 100644
--- a/chrome/browser/performance_manager/OWNERS
+++ b/chrome/browser/performance_manager/OWNERS
@@ -2,5 +2,9 @@
 fdoray@chromium.org
 siggi@chromium.org
 
+# For IPC security review
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
 # TEAM: catan-team@chromium.org
 # COMPONENT: Internals>ResourceCoordinator
diff --git a/chrome/browser/performance_manager/performance_manager.cc b/chrome/browser/performance_manager/performance_manager.cc
index 9445cb25..c81ca65 100644
--- a/chrome/browser/performance_manager/performance_manager.cc
+++ b/chrome/browser/performance_manager/performance_manager.cc
@@ -283,7 +283,7 @@
 }
 
 void PerformanceManager::BindWebUIGraphDump(
-    resource_coordinator::mojom::WebUIGraphDumpRequest request,
+    mojom::WebUIGraphDumpRequest request,
     const service_manager::BindSourceInfo& source_info) {
   std::unique_ptr<WebUIGraphDumpImpl> graph_dump =
       std::make_unique<WebUIGraphDumpImpl>(&graph_);
diff --git a/chrome/browser/performance_manager/performance_manager.h b/chrome/browser/performance_manager/performance_manager.h
index 7121d3f..bf69af53 100644
--- a/chrome/browser/performance_manager/performance_manager.h
+++ b/chrome/browser/performance_manager/performance_manager.h
@@ -130,9 +130,8 @@
   void BindInterfaceImpl(const std::string& interface_name,
                          mojo::ScopedMessagePipeHandle message_pipe);
 
-  void BindWebUIGraphDump(
-      resource_coordinator::mojom::WebUIGraphDumpRequest request,
-      const service_manager::BindSourceInfo& source_info);
+  void BindWebUIGraphDump(mojom::WebUIGraphDumpRequest request,
+                          const service_manager::BindSourceInfo& source_info);
   void OnGraphDumpConnectionError(WebUIGraphDumpImpl* graph_dump);
 
   InterfaceRegistry interface_registry_;
diff --git a/services/resource_coordinator/public/mojom/webui_graph_dump.mojom b/chrome/browser/performance_manager/webui_graph_dump.mojom
similarity index 79%
rename from services/resource_coordinator/public/mojom/webui_graph_dump.mojom
rename to chrome/browser/performance_manager/webui_graph_dump.mojom
index 8ace578..e71acf1 100644
--- a/services/resource_coordinator/public/mojom/webui_graph_dump.mojom
+++ b/chrome/browser/performance_manager/webui_graph_dump.mojom
@@ -5,10 +5,11 @@
 // This file exposes an interface to the chrome://discards Web UI to allow
 // viewing and exploring the CU graph.
 
-module resource_coordinator.mojom;
+module performance_manager.mojom;
 
 import "mojo/public/mojom/base/process_id.mojom";
 import "mojo/public/mojom/base/time.mojom";
+import "url/mojom/url.mojom";
 
 // Represents the momentary state of a Page CU.
 struct WebUIPageInfo {
@@ -16,7 +17,7 @@
 
   int64 main_frame_id;
 
-  string main_frame_url;
+  url.mojom.Url main_frame_url;
 
   // TODO(siggi): Estimate data.
 };
@@ -25,11 +26,7 @@
 struct WebUIFrameInfo {
   int64 id;
 
-  // TODO(chrisha): This should use url.mojom.Url, but that currently creates a
-  // cyclic dependency on blink/renderer/platform, which contains the KURL impl.
-  // Either break this cycle ourselves, or wait for KURL to be moved.
-  // crbug.com/768298
-  string url;
+  url.mojom.Url url;
   int64 parent_frame_id;
   int64 process_id;
 };
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.cc b/chrome/browser/performance_manager/webui_graph_dump_impl.cc
index 419d666..5ad5deb 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.cc
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl.cc
@@ -19,23 +19,20 @@
 
 WebUIGraphDumpImpl::~WebUIGraphDumpImpl() {}
 
-void WebUIGraphDumpImpl::Bind(
-    resource_coordinator::mojom::WebUIGraphDumpRequest request,
-    base::OnceClosure error_handler) {
+void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request,
+                              base::OnceClosure error_handler) {
   binding_.Bind(std::move(request));
   binding_.set_connection_error_handler(std::move(error_handler));
 }
 
 void WebUIGraphDumpImpl::GetCurrentGraph(GetCurrentGraphCallback callback) {
-  resource_coordinator::mojom::WebUIGraphPtr graph =
-      resource_coordinator::mojom::WebUIGraph::New();
+  mojom::WebUIGraphPtr graph = mojom::WebUIGraph::New();
 
   {
     auto processes = graph_->GetAllProcessNodes();
     graph->processes.reserve(processes.size());
     for (auto* process : processes) {
-      resource_coordinator::mojom::WebUIProcessInfoPtr process_info =
-          resource_coordinator::mojom::WebUIProcessInfo::New();
+      mojom::WebUIProcessInfoPtr process_info = mojom::WebUIProcessInfo::New();
 
       process_info->id = NodeBase::GetSerializationId(process);
       process_info->pid = process->process_id();
@@ -50,8 +47,7 @@
     auto frames = graph_->GetAllFrameNodes();
     graph->frames.reserve(frames.size());
     for (auto* frame : frames) {
-      resource_coordinator::mojom::WebUIFrameInfoPtr frame_info =
-          resource_coordinator::mojom::WebUIFrameInfo::New();
+      mojom::WebUIFrameInfoPtr frame_info = mojom::WebUIFrameInfo::New();
 
       frame_info->id = NodeBase::GetSerializationId(frame);
 
@@ -61,7 +57,7 @@
       auto* process = frame->process_node();
       frame_info->process_id = NodeBase::GetSerializationId(process);
 
-      frame_info->url = frame->url().spec();
+      frame_info->url = frame->url();
 
       graph->frames.push_back(std::move(frame_info));
     }
@@ -71,11 +67,10 @@
     auto pages = graph_->GetAllPageNodes();
     graph->pages.reserve(pages.size());
     for (auto* page : pages) {
-      resource_coordinator::mojom::WebUIPageInfoPtr page_info =
-          resource_coordinator::mojom::WebUIPageInfo::New();
+      mojom::WebUIPageInfoPtr page_info = mojom::WebUIPageInfo::New();
 
       page_info->id = NodeBase::GetSerializationId(page);
-      page_info->main_frame_url = page->main_frame_url().spec();
+      page_info->main_frame_url = page->main_frame_url();
 
       auto* main_frame = page->GetMainFrameNode();
       page_info->main_frame_id = NodeBase::GetSerializationId(main_frame);
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.h b/chrome/browser/performance_manager/webui_graph_dump_impl.h
index 514918c..d7ffa2f 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.h
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl.h
@@ -5,14 +5,14 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
 
+#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
-#include "services/resource_coordinator/public/mojom/webui_graph_dump.mojom.h"
 
 namespace performance_manager {
 
 class GraphImpl;
 
-class WebUIGraphDumpImpl : public resource_coordinator::mojom::WebUIGraphDump {
+class WebUIGraphDumpImpl : public mojom::WebUIGraphDump {
  public:
   explicit WebUIGraphDumpImpl(GraphImpl* graph);
   ~WebUIGraphDumpImpl() override;
@@ -21,12 +21,12 @@
   void GetCurrentGraph(GetCurrentGraphCallback callback) override;
 
   // Bind this instance to |request| with the |error_handler|.
-  void Bind(resource_coordinator::mojom::WebUIGraphDumpRequest request,
+  void Bind(mojom::WebUIGraphDumpRequest request,
             base::OnceClosure error_handler);
 
  private:
   GraphImpl* graph_;
-  mojo::Binding<resource_coordinator::mojom::WebUIGraphDump> binding_;
+  mojo::Binding<mojom::WebUIGraphDump> binding_;
 
   DISALLOW_COPY_AND_ASSIGN(WebUIGraphDumpImpl);
 };
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc b/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
index 99a8618..9c9e9138 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
@@ -31,12 +31,11 @@
 
   WebUIGraphDumpImpl impl(&graph);
 
-  resource_coordinator::mojom::WebUIGraphPtr returned_graph;
+  mojom::WebUIGraphPtr returned_graph;
   WebUIGraphDumpImpl::GetCurrentGraphCallback callback =
-      base::BindLambdaForTesting(
-          [&returned_graph](resource_coordinator::mojom::WebUIGraphPtr graph) {
-            returned_graph = std::move(graph);
-          });
+      base::BindLambdaForTesting([&returned_graph](mojom::WebUIGraphPtr graph) {
+        returned_graph = std::move(graph);
+      });
   impl.GetCurrentGraph(std::move(callback));
 
   task_env().RunUntilIdle();
diff --git a/chrome/browser/resources/discards/BUILD.gn b/chrome/browser/resources/discards/BUILD.gn
index a49c5cb9..abe3f6ba 100644
--- a/chrome/browser/resources/discards/BUILD.gn
+++ b/chrome/browser/resources/discards/BUILD.gn
@@ -50,8 +50,8 @@
 
 js_library("graph_tab") {
   deps = [
+    "//chrome/browser/performance_manager:mojo_bindings_js_library_for_compile",
     "//chrome/browser/ui/webui/discards:mojo_bindings_js_library_for_compile",
-    "//services/resource_coordinator/public/mojom:mojom_js_library_for_compile",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
   ]
@@ -59,7 +59,7 @@
 
 js_library("graph_doc") {
   deps = [
-    "//services/resource_coordinator/public/mojom:mojom_js_library_for_compile",
+    "//chrome/browser/performance_manager:mojo_bindings_js_library_for_compile",
   ]
 
   externs_list = [ "../../../../third_party/d3/src/externs.js" ]
diff --git a/chrome/browser/resources/discards/graph_doc.js b/chrome/browser/resources/discards/graph_doc.js
index 1cfcfe5..b072508 100644
--- a/chrome/browser/resources/discards/graph_doc.js
+++ b/chrome/browser/resources/discards/graph_doc.js
@@ -102,17 +102,18 @@
 }
 
 class PageNode extends GraphNode {
-  /** @param {!resourceCoordinator.mojom.WebUIPageInfo} page */
+  /** @param {!performanceManager.mojom.WebUIPageInfo} page */
   constructor(page) {
     super(page.id);
-    /** @type {!resourceCoordinator.mojom.WebUIPageInfo} */
+    /** @type {!performanceManager.mojom.WebUIPageInfo} */
     this.page = page;
     this.y = kPageNodesTargetY;
   }
 
   /** override */
   get title() {
-    return this.page.mainFrameUrl.length > 0 ? this.page.mainFrameUrl : 'Page';
+    return this.page.mainFrameUrl.url.length > 0 ? this.page.mainFrameUrl.url :
+                                                   'Page';
   }
 
   /** @override */
@@ -137,17 +138,17 @@
 }
 
 class FrameNode extends GraphNode {
-  /** @param {!resourceCoordinator.mojom.WebUIFrameInfo} frame */
+  /** @param {!performanceManager.mojom.WebUIFrameInfo} frame */
   constructor(frame) {
     super(frame.id);
-    /** @type {!resourceCoordinator.mojom.WebUIFrameInfo} frame */
+    /** @type {!performanceManager.mojom.WebUIFrameInfo} frame */
     this.frame = frame;
     this.color = this.selectColor(frame.processId);
   }
 
   /** override */
   get title() {
-    return this.frame.url.length > 0 ? this.frame.url : 'Frame';
+    return this.frame.url.url.length > 0 ? this.frame.url.url : 'Frame';
   }
 
   /** override */
@@ -167,10 +168,10 @@
 }
 
 class ProcessNode extends GraphNode {
-  /** @param {!resourceCoordinator.mojom.WebUIProcessInfo} process */
+  /** @param {!performanceManager.mojom.WebUIProcessInfo} process */
   constructor(process) {
     super(process.id);
-    /** @type {!resourceCoordinator.mojom.WebUIProcessInfo} */
+    /** @type {!performanceManager.mojom.WebUIProcessInfo} */
     this.process = process;
 
     this.color = this.selectColor(process.id);
@@ -391,7 +392,7 @@
 
   /**
    * @param {!Map<number, !GraphNode>} oldNodes
-   * @param {resourceCoordinator.mojom.WebUIPageInfo} page
+   * @param {performanceManager.mojom.WebUIPageInfo} page
    * @private
    */
   addOrUpdatePage_(oldNodes, page) {
@@ -411,7 +412,7 @@
 
   /**
    * @param {!Map<number, !GraphNode>} oldNodes
-   * @param {resourceCoordinator.mojom.WebUIFrameInfo} frame
+   * @param {performanceManager.mojom.WebUIFrameInfo} frame
    * @private
    */
   addOrUpdateFrame_(oldNodes, frame) {
@@ -431,7 +432,7 @@
 
   /**
    * @param {!Map<number, !GraphNode>} oldNodes
-   * @param {resourceCoordinator.mojom.WebUIProcessInfo} process
+   * @param {performanceManager.mojom.WebUIProcessInfo} process
    * @private
    */
   addOrUpdateProcess_(oldNodes, process) {
@@ -462,7 +463,7 @@
   }
 
   /**
-   * @param {resourceCoordinator.mojom.WebUIGraph} graph An updated graph from
+   * @param {performanceManager.mojom.WebUIGraph} graph An updated graph from
    *     the WebUI.
    * @private
    */
diff --git a/chrome/browser/resources/discards/graph_tab.js b/chrome/browser/resources/discards/graph_tab.js
index 4f4484a..8ba93727 100644
--- a/chrome/browser/resources/discards/graph_tab.js
+++ b/chrome/browser/resources/discards/graph_tab.js
@@ -8,7 +8,7 @@
   /**
    * The Mojo graph data source.
    *
-   * @private {resourceCoordinator.mojom.WebUIGraphDumpProxy}
+   * @private {performanceManager.mojom.WebUIGraphDumpProxy}
    */
   graphDump_: null,
 
@@ -17,7 +17,7 @@
 
   /** @override */
   ready: function() {
-    this.graphDump_ = resourceCoordinator.mojom.WebUIGraphDump.getProxy();
+    this.graphDump_ = performanceManager.mojom.WebUIGraphDump.getProxy();
   },
 
   /** @override */
diff --git a/chrome/browser/resources/discards/mojo_api.html b/chrome/browser/resources/discards/mojo_api.html
index 3b7bbda..2dd0baa 100644
--- a/chrome/browser/resources/discards/mojo_api.html
+++ b/chrome/browser/resources/discards/mojo_api.html
@@ -5,6 +5,7 @@
 -->
 <link rel="import" href="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.html">
 <link rel="import" href="chrome://resources/mojo/mojo/public/mojom/base/time.mojom.html">
+<link rel="import" href="chrome://resources/mojo/url/mojom/url.mojom.html">
 <script src="mojo/public/mojom/base/process_id.mojom-lite.js"></script>
 <script
     src="chrome/browser/resource_coordinator/lifecycle_unit_state.mojom-lite.js">
diff --git a/chrome/browser/resources/history/history_item.js b/chrome/browser/resources/history/history_item.js
index ec89a426..3e48c93 100644
--- a/chrome/browser/resources/history/history_item.js
+++ b/chrome/browser/resources/history/history_item.js
@@ -209,7 +209,7 @@
      */
     onMenuButtonTap_: function(e) {
       this.fire('open-menu', {
-        target: Polymer.dom(e).localTarget,
+        target: e.target,
         index: this.index,
         item: this.item,
       });
diff --git a/chrome/browser/resources/history/synced_device_card.js b/chrome/browser/resources/history/synced_device_card.js
index 6e1c5a6..083d18f 100644
--- a/chrome/browser/resources/history/synced_device_card.js
+++ b/chrome/browser/resources/history/synced_device_card.js
@@ -110,7 +110,7 @@
    */
   updateIcons_: function() {
     this.async(function() {
-      const icons = Polymer.dom(this.root).querySelectorAll('.website-icon');
+      const icons = this.shadowRoot.querySelectorAll('.website-icon');
 
       for (let i = 0; i < this.tabs.length; i++) {
         icons[i].style.backgroundImage = cr.icon.getFavicon(this.tabs[i].url);
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_section.js b/chrome/browser/resources/settings/autofill_page/autofill_section.js
index 530dcf0..652a4aa 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_section.js
+++ b/chrome/browser/resources/settings/autofill_page/autofill_section.js
@@ -166,7 +166,7 @@
     this.activeAddress = /** @type {!chrome.autofillPrivate.AddressEntry} */ (
         Object.assign({}, item));
 
-    const dotsButton = /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget);
+    const dotsButton = /** @type {!HTMLElement} */ (e.target);
     /** @type {!CrActionMenuElement} */ (this.$.addressSharedMenu)
         .showAt(dotsButton);
     this.activeDialogAnchor_ = dotsButton;
diff --git a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html
index 5508a8e..b10ede5 100644
--- a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html
+++ b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.html
@@ -57,15 +57,15 @@
       <div slot="title">$i18n{passwordDetailsTitle}</div>
       <div slot="body">
         <cr-input id="websiteInput" label="$i18n{editPasswordWebsiteLabel}"
-            value="[[item.entry.urls.link]]" readonly>
+            value="[[item.entry.urls.link]]" on-blur="onInputBlur_" readonly>
         </cr-input>
         <cr-input id="usernameInput" label="$i18n{editPasswordUsernameLabel}"
-            value="[[item.entry.username]]" readonly>
+            value="[[item.entry.username]]" on-blur="onInputBlur_" readonly>
         </cr-input>
-        <cr-input id="passwordInput" readonly
-            label="$i18n{editPasswordPasswordLabel}"
+        <cr-input id="passwordInput" label="$i18n{editPasswordPasswordLabel}"
             type="[[getPasswordInputType_(item.password)]]"
-            value="[[getPassword_(item.password)]]">
+            value="[[getPassword_(item.password)]]" on-blur="onInputBlur_"
+            readonly>
           <cr-icon-button id="showPasswordButton"
               class$="[[getIconClass_(item.password)]]" slot="suffix"
               hidden$="[[item.entry.federationText]]"
diff --git a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js
index 2b353fb..1e5923f1 100644
--- a/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js
+++ b/chrome/browser/resources/settings/autofill_page/password_edit_dialog.js
@@ -32,5 +32,10 @@
   onActionButtonTap_: function() {
     this.close();
   },
+
+  /** Manually de-select texts for readonly inputs. */
+  onInputBlur_: function() {
+    this.shadowRoot.getSelection().removeAllRanges();
+  },
 });
 })();
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_list.js b/chrome/browser/resources/settings/printing_page/cups_printers_list.js
index c92591a..0e266c7f 100644
--- a/chrome/browser/resources/settings/printing_page/cups_printers_list.js
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_list.js
@@ -46,8 +46,7 @@
     this.activePrinter = e.model.item;
     const menu =
         /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'));
-    menu.showAt(/** @type {!Element} */ (
-        Polymer.dom(/** @type {!Event} */ (e)).localTarget));
+    menu.showAt(/** @type {!Element} */ (/** @type {!Event} */ (e).target));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/site_settings/protocol_handlers.js b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
index 49e6a3ed..5fe1b81 100644
--- a/chrome/browser/resources/settings/site_settings/protocol_handlers.js
+++ b/chrome/browser/resources/settings/site_settings/protocol_handlers.js
@@ -207,8 +207,7 @@
     this.actionMenuModel_ = event.model.item;
     /** @type {!CrActionMenuElement} */ (this.$$('cr-action-menu'))
         .showAt(
-            /** @type {!Element} */ (
-                Polymer.dom(/** @type {!Event} */ (event)).localTarget));
+            /** @type {!Element} */ (/** @type {!Event} */ (event).target));
   },
 
   // <if expr="chromeos">
diff --git a/chrome/browser/resources/settings/site_settings/site_entry.js b/chrome/browser/resources/settings/site_settings/site_entry.js
index 412cbb0..ad60ba23 100644
--- a/chrome/browser/resources/settings/site_settings/site_entry.js
+++ b/chrome/browser/resources/settings/site_settings/site_entry.js
@@ -369,7 +369,7 @@
    */
   showOverflowMenu_: function(e) {
     this.fire('open-menu', {
-      target: Polymer.dom(e).localTarget,
+      target: e.target,
       index: this.listIndex,
       item: this.siteGroup,
     });
@@ -451,4 +451,4 @@
       };
     }
   },
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/ui/passwords/settings/password_manager_presenter_unittest.cc b/chrome/browser/ui/passwords/settings/password_manager_presenter_unittest.cc
index 6a5cabb..78810943 100644
--- a/chrome/browser/ui/passwords/settings/password_manager_presenter_unittest.cc
+++ b/chrome/browser/ui/passwords/settings/password_manager_presenter_unittest.cc
@@ -222,8 +222,8 @@
 }
 
 TEST_F(PasswordManagerPresenterTest, ChangeSavedPassword_UpdateDuplicates) {
-  AddPasswordEntry(GURL(kExampleCom), "user", "pass");
-  AddPasswordEntry(GURL(kExampleCom), "user", "pass");
+  AddPasswordEntry(GURL(std::string(kExampleCom) + "pathA"), "user", "pass");
+  AddPasswordEntry(GURL(std::string(kExampleCom) + "pathB"), "user", "pass");
   EXPECT_CALL(GetUIController(), SetPasswordList(SizeIs(1)));
   EXPECT_CALL(GetUIController(), SetPasswordExceptionList(IsEmpty()));
   UpdatePasswordLists();
diff --git a/chrome/browser/ui/webui/discards/BUILD.gn b/chrome/browser/ui/webui/discards/BUILD.gn
index 62abc88..83e8fb4 100644
--- a/chrome/browser/ui/webui/discards/BUILD.gn
+++ b/chrome/browser/ui/webui/discards/BUILD.gn
@@ -11,6 +11,7 @@
     ]
 
     public_deps = [
+      "//chrome/browser/performance_manager:mojo_bindings",
       "//chrome/browser/resource_coordinator:mojo_bindings",
     ]
   }
diff --git a/chrome/browser/ui/webui/discards/discards_ui.cc b/chrome/browser/ui/webui/discards/discards_ui.cc
index 330ca8e..1c776f1 100644
--- a/chrome/browser/ui/webui/discards/discards_ui.cc
+++ b/chrome/browser/ui/webui/discards/discards_ui.cc
@@ -451,7 +451,7 @@
 }
 
 void DiscardsUI::BindWebUIGraphDumpProvider(
-    resource_coordinator::mojom::WebUIGraphDumpRequest request) {
+    performance_manager::mojom::WebUIGraphDumpRequest request) {
   performance_manager::PerformanceManager* performance_manager =
       performance_manager::PerformanceManager::GetInstance();
   if (performance_manager) {
diff --git a/chrome/browser/ui/webui/discards/discards_ui.h b/chrome/browser/ui/webui/discards/discards_ui.h
index 4ba70c8e..1ec8c1c 100644
--- a/chrome/browser/ui/webui/discards/discards_ui.h
+++ b/chrome/browser/ui/webui/discards/discards_ui.h
@@ -8,8 +8,8 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
 #include "chrome/browser/ui/webui/discards/discards.mojom.h"
-#include "services/resource_coordinator/public/mojom/webui_graph_dump.mojom.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 
 namespace resource_coordinator {
@@ -27,7 +27,7 @@
   void BindDiscardsDetailsProvider(
       mojom::DiscardsDetailsProviderRequest request);
   void BindWebUIGraphDumpProvider(
-      resource_coordinator::mojom::WebUIGraphDumpRequest request);
+      performance_manager::mojom::WebUIGraphDumpRequest request);
 
   std::unique_ptr<mojom::DiscardsDetailsProvider> ui_handler_;
   resource_coordinator::LocalSiteCharacteristicsDataStoreInspector*
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9e890888..c7c936be 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3617,13 +3617,10 @@
       "../browser/media/router/providers/cast/cast_internal_message_util_unittest.cc",
       "../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc",
       "../browser/media/router/providers/cast/cast_media_route_provider_unittest.cc",
-      "../browser/media/router/providers/cast/cast_session_client_unittest.cc",
       "../browser/media/router/providers/cast/cast_session_tracker_unittest.cc",
       "../browser/media/router/providers/cast/dual_media_sink_service_unittest.cc",
       "../browser/media/router/providers/cast/mock_cast_activity_record.cc",
       "../browser/media/router/providers/cast/mock_cast_activity_record.h",
-      "../browser/media/router/providers/cast/test_util.cc",
-      "../browser/media/router/providers/cast/test_util.h",
       "../browser/media/router/providers/dial/dial_activity_manager_unittest.cc",
       "../browser/media/router/providers/dial/dial_internal_message_util_unittest.cc",
       "../browser/media/router/providers/dial/dial_media_route_provider_unittest.cc",
diff --git a/chrome/test/data/webui/cr_elements/cr_radio_group_test.js b/chrome/test/data/webui/cr_elements/cr_radio_group_test.js
index d80a59f..c483355 100644
--- a/chrome/test/data/webui/cr_elements/cr_radio_group_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_radio_group_test.js
@@ -96,11 +96,15 @@
     // Check for decrement.
     checkPressed(['Home', 'PageUp', 'ArrowUp', 'ArrowLeft'], 2, 1);
     // No change when reached first selected.
-    checkPressed(['Home', 'PageUp', 'ArrowUp', 'ArrowLeft'], 1, 1);
+    checkPressed(['Home'], 1, 1);
+    // Wraps when decrementing when first selected.
+    checkPressed(['PageUp', 'ArrowUp', 'ArrowLeft'], 1, 3);
     // Check for increment.
     checkPressed(['End', 'ArrowRight', 'PageDown', 'ArrowDown'], 2, 3);
     // No change when reached last selected.
-    checkPressed(['End', 'ArrowRight', 'PageDown', 'ArrowDown'], 3, 3);
+    checkPressed(['End'], 3, 3);
+    // Wraps when incrementing when last selected.
+    checkPressed(['ArrowRight', 'PageDown', 'ArrowDown'], 3, 1);
   });
 
   test('mouse event', () => {
diff --git a/chrome/test/data/webui/history/history_item_test.js b/chrome/test/data/webui/history/history_item_test.js
index d5c322a2..095efe19 100644
--- a/chrome/test/data/webui/history/history_item_test.js
+++ b/chrome/test/data/webui/history/history_item_test.js
@@ -79,7 +79,7 @@
     return PolymerTest.flushTasks().then(function() {
       Polymer.dom.flush();
       // Check that the correct number of time gaps are inserted.
-      const items = Polymer.dom(element.root).querySelectorAll('history-item');
+      const items = element.shadowRoot.querySelectorAll('history-item');
 
       assertTrue(items[0].hasTimeGap);
       assertTrue(items[1].hasTimeGap);
@@ -96,7 +96,7 @@
 
     return PolymerTest.flushTasks().then(function() {
       Polymer.dom.flush();
-      const items = Polymer.dom(element.root).querySelectorAll('history-item');
+      const items = element.shadowRoot.querySelectorAll('history-item');
 
       assertTrue(items[0].hasTimeGap);
       assertFalse(items[1].hasTimeGap);
@@ -108,7 +108,7 @@
     element.addNewResults(TEST_HISTORY_RESULTS);
     return PolymerTest.flushTasks().then(function() {
       Polymer.dom.flush();
-      const items = Polymer.dom(element.root).querySelectorAll('history-item');
+      const items = element.shadowRoot.querySelectorAll('history-item');
 
       element.removeItemsByIndex_([3]);
       assertEquals(5, element.historyData_.length);
@@ -132,8 +132,7 @@
           return PolymerTest.flushTasks();
         })
         .then(function() {
-
-          items = Polymer.dom(element.root).querySelectorAll('history-item');
+          items = element.shadowRoot.querySelectorAll('history-item');
 
           items[1].$$('#bookmark-star').focus();
           MockInteractions.tap(items[1].$$('#bookmark-star'));
diff --git a/chrome/test/data/webui/history/history_supervised_user_test.js b/chrome/test/data/webui/history/history_supervised_user_test.js
index d292248..2279f467 100644
--- a/chrome/test/data/webui/history/history_supervised_user_test.js
+++ b/chrome/test/data/webui/history/history_supervised_user_test.js
@@ -22,8 +22,7 @@
 
   test('checkboxes disabled for supervised user', function() {
     return PolymerTest.flushTasks().then(function() {
-      const items =
-          Polymer.dom(historyList.root).querySelectorAll('history-item');
+      const items = historyList.shadowRoot.querySelectorAll('history-item');
 
       MockInteractions.tap(items[0].$['checkbox']);
 
diff --git a/chrome/test/data/webui/history/history_synced_tabs_test.js b/chrome/test/data/webui/history/history_synced_tabs_test.js
index d9c94496..3296ea4 100644
--- a/chrome/test/data/webui/history/history_synced_tabs_test.js
+++ b/chrome/test/data/webui/history/history_synced_tabs_test.js
@@ -43,8 +43,7 @@
       const card = element.$$('history-synced-device-card');
       assertEquals(
           'http://www.google.com',
-          Polymer.dom(card.root)
-              .querySelectorAll('.website-title')[0]
+          card.shadowRoot.querySelectorAll('.website-title')[0]
               .textContent.trim());
       assertEquals(2, card.tabs.length);
     });
@@ -108,8 +107,8 @@
           // Check that the actual link changes.
           assertEquals(
               'http://crbug.com/new',
-              Polymer.dom(cards[0].root)
-                  .querySelectorAll('.website-title')[1]
+              cards[0]
+                  .shadowRoot.querySelectorAll('.website-title')[1]
                   .textContent.trim());
         });
   });
@@ -156,8 +155,8 @@
           // Ensure the title text is rendered during searches.
           assertEquals(
               'http://www.google.com',
-              Polymer.dom(cards[0].root)
-                  .querySelectorAll('.website-title')[0]
+              cards[0]
+                  .shadowRoot.querySelectorAll('.website-title')[0]
                   .textContent.trim());
 
           element.searchTerm = 'Sans';
diff --git a/chrome/test/data/webui/settings/all_sites_tests.js b/chrome/test/data/webui/settings/all_sites_tests.js
index 5abc3a6..e83ee91 100644
--- a/chrome/test/data/webui/settings/all_sites_tests.js
+++ b/chrome/test/data/webui/settings/all_sites_tests.js
@@ -150,8 +150,8 @@
           Polymer.dom.flush();
           const siteEntries =
               testElement.$.listContainer.querySelectorAll('site-entry');
-          const hiddenSiteEntries = Polymer.dom(testElement.root)
-                                        .querySelectorAll('site-entry[hidden]');
+          const hiddenSiteEntries =
+              testElement.shadowRoot.querySelectorAll('site-entry[hidden]');
           assertEquals(1, siteEntries.length - hiddenSiteEntries.length);
 
           for (let i = 0; i < siteEntries; ++i) {
diff --git a/chrome/test/data/webui/settings/certificate_manager_test.js b/chrome/test/data/webui/settings/certificate_manager_test.js
index d61fefb..785dcba 100644
--- a/chrome/test/data/webui/settings/certificate_manager_test.js
+++ b/chrome/test/data/webui/settings/certificate_manager_test.js
@@ -302,8 +302,7 @@
     test('DeleteSuccess', function() {
       assertTrue(dialog.$.dialog.open);
       // Check that the dialog title includes the certificate name.
-      const titleEl =
-          Polymer.dom(dialog.$.dialog).querySelector('[slot=title]');
+      const titleEl = dialog.$.dialog.querySelector('[slot=title]');
       assertTrue(titleEl.textContent.includes(model.name));
 
       // Simulate clicking 'OK'.
@@ -359,7 +358,7 @@
 
     test('EncryptSuccess', function() {
       const passwordInputElements =
-          Polymer.dom(dialog.$.dialog).querySelectorAll('cr-input');
+          dialog.$.dialog.querySelectorAll('cr-input');
       const passwordInputElement = passwordInputElements[0];
       const confirmPasswordInputElement = passwordInputElements[1];
 
@@ -394,7 +393,7 @@
       browserProxy.forceCertificatesError();
 
       const passwordInputElements =
-          Polymer.dom(dialog.$.dialog).querySelectorAll('cr-input');
+          dialog.$.dialog.querySelectorAll('cr-input');
       const passwordInputElement = passwordInputElements[0];
       passwordInputElement.value = 'foopassword';
       const confirmPasswordInputElement = passwordInputElements[1];
@@ -434,8 +433,7 @@
     });
 
     test('DecryptSuccess', function() {
-      const passwordInputElement =
-          Polymer.dom(dialog.$.dialog).querySelector('cr-input');
+      const passwordInputElement = dialog.$.dialog.querySelector('cr-input');
       assertTrue(dialog.$.dialog.open);
 
       // Test that the 'OK' button is enabled even when the password field is
@@ -459,8 +457,7 @@
     test('DecryptError', function() {
       browserProxy.forceCertificatesError();
       // Simulate entering some password.
-      const passwordInputElement =
-          Polymer.dom(dialog.$.dialog).querySelector('cr-input');
+      const passwordInputElement = dialog.$.dialog.querySelector('cr-input');
       passwordInputElement.value = 'foopassword';
       triggerInputEvent(passwordInputElement);
 
diff --git a/chrome/test/data/webui/settings/search_engines_page_test.js b/chrome/test/data/webui/settings/search_engines_page_test.js
index a610387a..70c6ec58 100644
--- a/chrome/test/data/webui/settings/search_engines_page_test.js
+++ b/chrome/test/data/webui/settings/search_engines_page_test.js
@@ -340,14 +340,13 @@
         // if IronList.items instead of the child nodes.
         Polymer.dom.flush();
         const defaultsList = searchEnginesLists[0];
-        const defaultsEntries = Polymer.dom(defaultsList.shadowRoot)
-                                    .querySelector('iron-list')
-                                    .items;
+        const defaultsEntries =
+            defaultsList.shadowRoot.querySelector('iron-list').items;
         assertEquals(searchEnginesInfo.defaults.length, defaultsEntries.length);
 
         const othersList = searchEnginesLists[1];
         const othersEntries =
-            Polymer.dom(othersList.shadowRoot).querySelector('iron-list').items;
+            othersList.shadowRoot.querySelector('iron-list').items;
         assertEquals(searchEnginesInfo.others.length, othersEntries.length);
 
         // Ensure that the search engines have reverse alphabetical order in the
@@ -360,7 +359,7 @@
         assertEquals(searchEnginesInfo.others[0].name, othersEntries[1].name);
 
         const extensionEntries =
-            Polymer.dom(page.shadowRoot).querySelector('iron-list').items;
+            page.shadowRoot.querySelector('iron-list').items;
         assertEquals(
             searchEnginesInfo.extensions.length, extensionEntries.length);
       });
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.js
index 79ced1fe..141797f 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.js
@@ -228,7 +228,7 @@
     const api =
         document.createElement('mock-website-usage-private-api-storage');
     testElement.$.usageApi = api;
-    Polymer.dom(parent).appendChild(api);
+    parent.appendChild(api);
     Polymer.dom.flush();
 
     // Call onOriginChanged_() manually to simulate a new navigation.
@@ -272,7 +272,7 @@
     const api =
         document.createElement('mock-website-usage-private-api-cookies');
     testElement.$.usageApi = api;
-    Polymer.dom(parent).appendChild(api);
+    parent.appendChild(api);
     Polymer.dom.flush();
 
     // Call onOriginChanged_() manually to simulate a new navigation.
@@ -394,7 +394,7 @@
     });
     let api = document.createElement('mock1-website-usage-private-api');
     testElement.$.usageApi = api;
-    Polymer.dom(parent).appendChild(api);
+    parent.appendChild(api);
     Polymer.dom.flush();
 
     // Check both cancelling and accepting the dialog closes it.
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index c655efce..6cb5328 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -234,16 +234,6 @@
     # To disable a specific test, add it the following list.
     tast_disabled_tests = []
   }
-  tast_test("chrome_kevin_tast_tests") {
-    # Tests that fail on Kevin devices.
-    tast_disabled_tests = [
-      # Disabled due to misleading USE flags on full boards. crbug.com/916367
-      "arc.Boot",
-      "arc.BuildProperties",
-      "security.NetworkListenersARC",
-      "video.WebRTCPeerConnCameraVP8",  # crbug.com/923061
-    ]
-  }
 
   group("cros_chrome_deploy") {
     # The following run-time dependencies are needed to deploy chrome to a
diff --git a/components/contextual_search/core/browser/public.cc b/components/contextual_search/core/browser/public.cc
index e5acbd8..42755adf 100644
--- a/components/contextual_search/core/browser/public.cc
+++ b/components/contextual_search/core/browser/public.cc
@@ -7,8 +7,14 @@
 namespace contextual_search {
 
 const char kContextualSearchFieldTrialName[] = "ContextualSearch";
-const char kContextualCardsVersionParamName[] = "contextual_cards_version";
 
+// Longpress Resolve variations:
+const char kLongpressResolveParamName[] = "longpress_resolve_variation";
+const char kLongpressResolveHideOnScroll[] = "1";
+const char kLongpressResolvePrivacyAggressive[] = "2";
+
+// Contextual Cards variations and integration Api settings.
+const char kContextualCardsVersionParamName[] = "contextual_cards_version";
 // The version of the Contextual Cards API that we want to invoke.
 const int kContextualCardsEntityIntegration = 1;
 const int kContextualCardsQuickActionsIntegration = 2;
diff --git a/components/contextual_search/core/browser/public.h b/components/contextual_search/core/browser/public.h
index 48e619c0..08181a7 100644
--- a/components/contextual_search/core/browser/public.h
+++ b/components/contextual_search/core/browser/public.h
@@ -37,6 +37,12 @@
 // String form of kContextualCardsSimplifiedServerMixin +
 // kContextualCardsDiagnosticIntegration.
 extern const char kContextualCardsSimplifiedServerWithDiagnosticChar[];
+
+// Longpress resolve variations:
+extern const char kLongpressResolveParamName[];
+extern const char kLongpressResolveHideOnScroll[];
+extern const char kLongpressResolvePrivacyAggressive[];
+
 }  // namespace contextual_search
 
 #endif  // COMPONENTS_CONTEXTUAL_SEARCH_CORE_BROWSER_PUBLIC_H_
diff --git a/components/gwp_asan/client/sampling_malloc_shims.cc b/components/gwp_asan/client/sampling_malloc_shims.cc
index 94f6590b..4e23af21 100644
--- a/components/gwp_asan/client/sampling_malloc_shims.cc
+++ b/components/gwp_asan/client/sampling_malloc_shims.cc
@@ -252,7 +252,7 @@
                         size_t num_metadata,
                         size_t total_pages,
                         size_t sampling_frequency) {
-  static crash_reporter::CrashKeyString<24> malloc_crash_key(kGpaCrashKey);
+  static crash_reporter::CrashKeyString<24> malloc_crash_key(kMallocCrashKey);
   gpa = new GuardedPageAllocator();
   gpa->Init(max_allocated_pages, num_metadata, total_pages);
   malloc_crash_key.Set(gpa->GetCrashKey());
diff --git a/components/gwp_asan/client/sampling_malloc_shims_unittest.cc b/components/gwp_asan/client/sampling_malloc_shims_unittest.cc
index 8d5d7aa..aaaea76 100644
--- a/components/gwp_asan/client/sampling_malloc_shims_unittest.cc
+++ b/components/gwp_asan/client/sampling_malloc_shims_unittest.cc
@@ -229,7 +229,7 @@
 MULTIPROCESS_TEST_MAIN_WITH_SETUP(
     CrashKey,
     SamplingMallocShimsTest::multiprocessTestSetup) {
-  if (crash_reporter::GetCrashKeyValue(kGpaCrashKey) !=
+  if (crash_reporter::GetCrashKeyValue(kMallocCrashKey) !=
       GetMallocGpaForTesting().GetCrashKey()) {
     return kFailure;
   }
diff --git a/components/gwp_asan/common/crash_key_name.h b/components/gwp_asan/common/crash_key_name.h
index 50cf366b..b4e8562 100644
--- a/components/gwp_asan/common/crash_key_name.h
+++ b/components/gwp_asan/common/crash_key_name.h
@@ -8,9 +8,9 @@
 namespace gwp_asan {
 namespace internal {
 
-// The name of the crash key used to convey the address of the
-// AllocatorBaseState to the crash handler.
-const char kGpaCrashKey[] = "allocator-base-state-address";
+// The name of the crash key used to convey the address of the AllocatorState
+// for the malloc hooks to the crash handler.
+const char kMallocCrashKey[] = "gwp-asan-malloc";
 
 }  // namespace internal
 }  // namespace gwp_asan
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.cc b/components/gwp_asan/crash_handler/crash_analyzer.cc
index 531efbbf..7f4652d6 100644
--- a/components/gwp_asan/crash_handler/crash_analyzer.cc
+++ b/components/gwp_asan/crash_handler/crash_analyzer.cc
@@ -73,7 +73,7 @@
     const crashpad::ProcessSnapshot& process_snapshot) {
   for (auto* module : process_snapshot.Modules()) {
     for (auto annotation : module->AnnotationObjects()) {
-      if (annotation.name != kGpaCrashKey)
+      if (annotation.name != kMallocCrashKey)
         continue;
 
       if (annotation.type !=
diff --git a/components/gwp_asan/crash_handler/crash_handler_unittest.cc b/components/gwp_asan/crash_handler/crash_handler_unittest.cc
index 1aafd2e..d529a5b 100644
--- a/components/gwp_asan/crash_handler/crash_handler_unittest.cc
+++ b/components/gwp_asan/crash_handler/crash_handler_unittest.cc
@@ -87,7 +87,7 @@
 
   std::string gpa_addr = gpa->GetCrashKey();
   static crashpad::Annotation gpa_annotation(
-      crashpad::Annotation::Type::kString, kGpaCrashKey,
+      crashpad::Annotation::Type::kString, kMallocCrashKey,
       const_cast<char*>(gpa_addr.c_str()));
   gpa_annotation.SetSize(gpa_addr.size());
 
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index ab14de2..0ecdde87 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -43,6 +43,8 @@
     "android_affiliation/lookup_affiliation_response_parser.cc",
     "android_affiliation/lookup_affiliation_response_parser.h",
     "android_affiliation/test_affiliation_fetcher_factory.h",
+    "blacklisted_credentials_cleaner.cc",
+    "blacklisted_credentials_cleaner.h",
     "browser_save_password_progress_logger.cc",
     "browser_save_password_progress_logger.h",
     "credential_manager_impl.cc",
@@ -420,6 +422,7 @@
     "android_affiliation/affiliation_service_unittest.cc",
     "android_affiliation/affiliation_utils_unittest.cc",
     "android_affiliation/facet_manager_unittest.cc",
+    "blacklisted_credentials_cleaner_unittest.cc",
     "browser_save_password_progress_logger_unittest.cc",
     "credential_manager_impl_unittest.cc",
     "credential_manager_logger_unittest.cc",
diff --git a/components/password_manager/core/browser/blacklisted_credentials_cleaner.cc b/components/password_manager/core/browser/blacklisted_credentials_cleaner.cc
new file mode 100644
index 0000000..ece8b21f
--- /dev/null
+++ b/components/password_manager/core/browser/blacklisted_credentials_cleaner.cc
@@ -0,0 +1,67 @@
+// 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 "components/password_manager/core/browser/blacklisted_credentials_cleaner.h"
+
+#include <set>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
+#include "components/password_manager/core/browser/password_store.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace password_manager {
+
+BlacklistedCredentialsCleaner::BlacklistedCredentialsCleaner(
+    scoped_refptr<PasswordStore> store,
+    PrefService* prefs)
+    : store_(std::move(store)), prefs_(prefs) {}
+
+BlacklistedCredentialsCleaner::~BlacklistedCredentialsCleaner() = default;
+
+bool BlacklistedCredentialsCleaner::NeedsCleaning() {
+  const bool needs_cleaning =
+      !prefs_->GetBoolean(prefs::kBlacklistedCredentialsNormalized);
+  base::UmaHistogramBoolean(
+      "PasswordManager.BlacklistedSites.NeedNormalization", needs_cleaning);
+  return needs_cleaning;
+}
+
+void BlacklistedCredentialsCleaner::StartCleaning(Observer* observer) {
+  observer_ = observer;
+  store_->GetBlacklistLogins(this);
+}
+
+void BlacklistedCredentialsCleaner::OnGetPasswordStoreResults(
+    std::vector<std::unique_ptr<autofill::PasswordForm>> results) {
+  std::set<std::string> signon_realms;
+  for (const auto& result : results) {
+    DCHECK(result->blacklisted_by_user);
+    if (!signon_realms.insert(result->signon_realm).second) {
+      // Insertion failed due to already existing signon realm, so we remove the
+      // duplicated entry from the store.
+      store_->RemoveLogin(*result);
+      continue;
+    }
+
+    autofill::PasswordForm blacklisted =
+        password_manager_util::MakeNormalizedBlacklistedForm(
+            PasswordStore::FormDigest(*result));
+    blacklisted.date_created = result->date_created;
+    // In case |blacklisted| and |result| differ, update the store.
+    if (!ArePasswordFormUniqueKeyEqual(blacklisted, *result))
+      store_->UpdateLoginWithPrimaryKey(blacklisted, *result);
+    else if (blacklisted != *result)
+      store_->UpdateLogin(blacklisted);
+  }
+
+  prefs_->SetBoolean(prefs::kBlacklistedCredentialsNormalized, true);
+  observer_->CleaningCompleted();
+}
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/blacklisted_credentials_cleaner.h b/components/password_manager/core/browser/blacklisted_credentials_cleaner.h
new file mode 100644
index 0000000..f00b16e4
--- /dev/null
+++ b/components/password_manager/core/browser/blacklisted_credentials_cleaner.h
@@ -0,0 +1,59 @@
+// 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 COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BLACKLISTED_CREDENTIALS_CLEANER_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BLACKLISTED_CREDENTIALS_CLEANER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "components/password_manager/core/browser/credentials_cleaner.h"
+#include "components/password_manager/core/browser/password_store_consumer.h"
+
+class PrefService;
+
+namespace autofill {
+struct PasswordForm;
+}
+
+namespace password_manager {
+
+class PasswordStore;
+
+// This class is responsible for cleaning up blacklisted credentials. In
+// particular, it does the following operations:
+// - De-duplicate existing blacklisted credentials by making sure there is at
+//   most one blacklisted credential per signon realm.
+// - Normalize existing blacklisted credentials by clearing out all data except
+//   scheme, signon realm and origin.
+class BlacklistedCredentialsCleaner : public CredentialsCleaner,
+                                      public PasswordStoreConsumer {
+ public:
+  BlacklistedCredentialsCleaner(scoped_refptr<PasswordStore> store,
+                                PrefService* prefs);
+  BlacklistedCredentialsCleaner(const BlacklistedCredentialsCleaner&) = delete;
+  BlacklistedCredentialsCleaner& operator=(
+      const BlacklistedCredentialsCleaner&) = delete;
+  ~BlacklistedCredentialsCleaner() override;
+
+  // CredentialsCleaner:
+  bool NeedsCleaning() override;
+  void StartCleaning(Observer* observer) override;
+
+ private:
+  // PasswordStoreConsumer:
+  void OnGetPasswordStoreResults(
+      std::vector<std::unique_ptr<autofill::PasswordForm>> results) override;
+
+  const scoped_refptr<PasswordStore> store_;
+
+  PrefService* prefs_ = nullptr;
+
+  Observer* observer_ = nullptr;
+};
+
+}  // namespace password_manager
+
+#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BLACKLISTED_CREDENTIALS_CLEANER_H_
diff --git a/components/password_manager/core/browser/blacklisted_credentials_cleaner_unittest.cc b/components/password_manager/core/browser/blacklisted_credentials_cleaner_unittest.cc
new file mode 100644
index 0000000..96a63bf3
--- /dev/null
+++ b/components/password_manager/core/browser/blacklisted_credentials_cleaner_unittest.cc
@@ -0,0 +1,174 @@
+// 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 "components/password_manager/core/browser/blacklisted_credentials_cleaner.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/time/time.h"
+#include "components/password_manager/core/browser/test_password_store.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/sync/model/syncable_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace password_manager {
+
+namespace {
+
+using autofill::PasswordForm;
+using ::testing::ElementsAre;
+
+constexpr char kTestURL[] = "https://example.com/login/";
+constexpr char kTestUsername[] = "Username";
+constexpr char kTestUsernameElement[] = "username_element";
+constexpr char kTestUsername2[] = "Username2";
+constexpr char kTestPassword[] = "12345";
+constexpr char kTestPasswordElement[] = "password_element";
+constexpr char kTestSubmitElement[] = "submit_element";
+// An arbitrary creation date different from a default constructed base::Time().
+const base::Time kTestCreationDate =
+    base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromDays(1));
+
+PasswordForm GetTestCredential() {
+  PasswordForm form;
+  form.scheme = PasswordForm::SCHEME_HTML;
+  form.signon_realm = GURL(kTestURL).GetOrigin().spec();
+  form.origin = GURL(kTestURL);
+  form.username_element = base::ASCIIToUTF16(kTestUsernameElement);
+  form.username_value = base::ASCIIToUTF16(kTestUsername);
+  form.password_element = base::ASCIIToUTF16(kTestPasswordElement);
+  form.password_value = base::ASCIIToUTF16(kTestPassword);
+  form.submit_element = base::ASCIIToUTF16(kTestSubmitElement);
+  form.date_created = kTestCreationDate;
+  return form;
+}
+
+}  // namespace
+
+class MockCredentialsCleanerObserver : public CredentialsCleaner::Observer {
+ public:
+  MOCK_METHOD0(CleaningCompleted, void());
+};
+
+class BlacklistedCredentialsCleanerTest : public ::testing::Test {
+ public:
+  BlacklistedCredentialsCleanerTest() {
+    EXPECT_TRUE(
+        store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
+    prefs_.registry()->RegisterBooleanPref(
+        prefs::kBlacklistedCredentialsNormalized, false);
+  }
+
+  BlacklistedCredentialsCleanerTest(const BlacklistedCredentialsCleanerTest&) =
+      delete;
+  BlacklistedCredentialsCleanerTest& operator=(
+      const BlacklistedCredentialsCleanerTest&) = delete;
+
+  ~BlacklistedCredentialsCleanerTest() override {
+    store_->ShutdownOnUIThread();
+    scoped_task_environment_.RunUntilIdle();
+  }
+
+ protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::HistogramTester histogram_tester_;
+  const scoped_refptr<TestPasswordStore> store_ =
+      base::MakeRefCounted<TestPasswordStore>();
+  TestingPrefServiceSimple prefs_;
+  BlacklistedCredentialsCleaner cleaner_{store_, &prefs_};
+};
+
+TEST_F(BlacklistedCredentialsCleanerTest, NoCleaningWhenPrefIsSet) {
+  prefs_.SetBoolean(prefs::kBlacklistedCredentialsNormalized, true);
+  EXPECT_FALSE(cleaner_.NeedsCleaning());
+  histogram_tester_.ExpectUniqueSample(
+      "PasswordManager.BlacklistedSites.NeedNormalization", false, 1);
+}
+
+TEST_F(BlacklistedCredentialsCleanerTest, CleanerUpdatesPref) {
+  EXPECT_TRUE(cleaner_.NeedsCleaning());
+  histogram_tester_.ExpectUniqueSample(
+      "PasswordManager.BlacklistedSites.NeedNormalization", true, 1);
+
+  MockCredentialsCleanerObserver observer;
+  cleaner_.StartCleaning(&observer);
+  EXPECT_CALL(observer, CleaningCompleted);
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_TRUE(prefs_.GetBoolean(prefs::kBlacklistedCredentialsNormalized));
+}
+
+TEST_F(BlacklistedCredentialsCleanerTest, CleanerNormalizesData) {
+  PasswordForm test_credential = GetTestCredential();
+  test_credential.blacklisted_by_user = true;
+  store_->AddLogin(test_credential);
+
+  MockCredentialsCleanerObserver observer;
+  cleaner_.StartCleaning(&observer);
+  EXPECT_CALL(observer, CleaningCompleted);
+  scoped_task_environment_.RunUntilIdle();
+
+  TestPasswordStore::PasswordMap stored_passwords = store_->stored_passwords();
+  ASSERT_EQ(1u, stored_passwords.size());
+
+  PasswordForm blacklisted_form;
+  blacklisted_form.blacklisted_by_user = true;
+  blacklisted_form.scheme = PasswordForm::SCHEME_HTML;
+  blacklisted_form.signon_realm = GURL(kTestURL).GetOrigin().spec();
+  blacklisted_form.origin = GURL(kTestURL).GetOrigin();
+  blacklisted_form.date_created = kTestCreationDate;
+  EXPECT_THAT(stored_passwords.begin()->second, ElementsAre(blacklisted_form));
+}
+
+TEST_F(BlacklistedCredentialsCleanerTest,
+       CleanerDoesNotModifyNonBlacklistedEntries) {
+  PasswordForm test_credential = GetTestCredential();
+  store_->AddLogin(test_credential);
+
+  MockCredentialsCleanerObserver observer;
+  cleaner_.StartCleaning(&observer);
+  EXPECT_CALL(observer, CleaningCompleted);
+  scoped_task_environment_.RunUntilIdle();
+
+  TestPasswordStore::PasswordMap stored_passwords = store_->stored_passwords();
+  ASSERT_EQ(1u, stored_passwords.size());
+  EXPECT_THAT(stored_passwords.begin()->second, ElementsAre(test_credential));
+}
+
+TEST_F(BlacklistedCredentialsCleanerTest, CleanerDeduplicatesForms) {
+  PasswordForm test_credential = GetTestCredential();
+  test_credential.blacklisted_by_user = true;
+  store_->AddLogin(test_credential);
+
+  // Create a duplicated entry for the same signon realm.
+  test_credential.username_value = base::ASCIIToUTF16(kTestUsername2);
+  store_->AddLogin(test_credential);
+  scoped_task_environment_.RunUntilIdle();
+
+  const TestPasswordStore::PasswordMap& stored_passwords =
+      store_->stored_passwords();
+  ASSERT_EQ(1u, stored_passwords.size());
+  ASSERT_EQ(2u, stored_passwords.begin()->second.size());
+
+  MockCredentialsCleanerObserver observer;
+  cleaner_.StartCleaning(&observer);
+  EXPECT_CALL(observer, CleaningCompleted);
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_EQ(1u, stored_passwords.size());
+  // Only one credential for the signon realm is left.
+  PasswordForm blacklisted_form;
+  blacklisted_form.blacklisted_by_user = true;
+  blacklisted_form.scheme = PasswordForm::SCHEME_HTML;
+  blacklisted_form.signon_realm = GURL(kTestURL).GetOrigin().spec();
+  blacklisted_form.origin = GURL(kTestURL).GetOrigin();
+  blacklisted_form.date_created = kTestCreationDate;
+  EXPECT_THAT(stored_passwords.begin()->second, ElementsAre(blacklisted_form));
+}
+
+}  // namespace password_manager
diff --git a/components/password_manager/core/browser/credential_manager_impl_unittest.cc b/components/password_manager/core/browser/credential_manager_impl_unittest.cc
index a1ccd31e..8e9b331 100644
--- a/components/password_manager/core/browser/credential_manager_impl_unittest.cc
+++ b/components/password_manager/core/browser/credential_manager_impl_unittest.cc
@@ -1548,7 +1548,6 @@
   blacklisted.blacklisted_by_user = true;
   blacklisted.origin = form_.origin;
   blacklisted.signon_realm = form_.signon_realm;
-  blacklisted.type = autofill::PasswordForm::TYPE_API;
   blacklisted.date_created = passwords[form_.signon_realm][0].date_created;
   EXPECT_THAT(passwords[form_.signon_realm], testing::ElementsAre(blacklisted));
 }
@@ -1577,7 +1576,6 @@
   blacklisted.blacklisted_by_user = true;
   blacklisted.origin = form_.origin;
   blacklisted.signon_realm = blacklisted.origin.spec();
-  blacklisted.type = autofill::PasswordForm::TYPE_API;
   blacklisted.date_created =
       passwords[blacklisted.signon_realm][0].date_created;
   EXPECT_THAT(passwords[blacklisted.signon_realm],
diff --git a/components/password_manager/core/browser/form_saver_impl.cc b/components/password_manager/core/browser/form_saver_impl.cc
index d00c1ea..e879aed 100644
--- a/components/password_manager/core/browser/form_saver_impl.cc
+++ b/components/password_manager/core/browser/form_saver_impl.cc
@@ -10,6 +10,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/password_store.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -50,16 +51,9 @@
 FormSaverImpl::~FormSaverImpl() = default;
 
 void FormSaverImpl::PermanentlyBlacklist(PasswordForm* observed) {
-  observed->preferred = false;
-  observed->blacklisted_by_user = true;
-  observed->username_value.clear();
-  observed->username_element.clear();
-  observed->password_value.clear();
-  observed->password_element.clear();
-  observed->other_possible_usernames.clear();
+  *observed = password_manager_util::MakeNormalizedBlacklistedForm(
+      PasswordStore::FormDigest(*observed));
   observed->date_created = base::Time::Now();
-  observed->origin = observed->origin.GetOrigin();
-
   store_->AddLogin(*observed);
 }
 
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index a77b739..14c6a46 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -353,6 +353,8 @@
 // static
 void PasswordManager::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterBooleanPref(prefs::kBlacklistedCredentialsNormalized,
+                                false);
   registry->RegisterBooleanPref(
       prefs::kCredentialsEnableService, true,
       user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index db8c51d2..82907a0c 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -17,6 +17,8 @@
 #include "components/autofill/core/browser/popup_item_ids.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/autofill/core/common/password_generation_util.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/blacklisted_credentials_cleaner.h"
 #include "components/password_manager/core/browser/credentials_cleaner.h"
 #include "components/password_manager/core/browser/credentials_cleaner_runner.h"
 #include "components/password_manager/core/browser/http_credentials_cleaner.h"
@@ -188,6 +190,10 @@
   }
 #endif  // !defined(OS_IOS)
 
+  cleaning_tasks_runner->MaybeAddCleaningTask(
+      std::make_unique<password_manager::BlacklistedCredentialsCleaner>(store,
+                                                                        prefs));
+
   if (cleaning_tasks_runner->HasPendingTasks()) {
     // The runner will delete itself once the clearing tasks are done, thus we
     // are releasing ownership here.
@@ -294,4 +300,26 @@
   return credentials.empty() ? nullptr : credentials.begin()->second;
 }
 
+autofill::PasswordForm MakeNormalizedBlacklistedForm(
+    password_manager::PasswordStore::FormDigest digest) {
+  autofill::PasswordForm result;
+  result.blacklisted_by_user = true;
+  result.scheme = std::move(digest.scheme);
+  result.signon_realm = std::move(digest.signon_realm);
+  // In case |digest| corresponds to an Android credential copy the origin as
+  // is, otherwise clear out the path by calling GetOrigin().
+  if (password_manager::FacetURI::FromPotentiallyInvalidSpec(
+          digest.origin.spec())
+          .IsValidAndroidFacetURI()) {
+    result.origin = std::move(digest.origin);
+  } else {
+    // GetOrigin() will return an empty GURL if the origin is not valid or
+    // standard. DCHECK that this will not happen.
+    DCHECK(digest.origin.is_valid());
+    DCHECK(digest.origin.IsStandard());
+    result.origin = digest.origin.GetOrigin();
+  }
+  return result;
+}
+
 }  // namespace password_manager_util
diff --git a/components/password_manager/core/browser/password_manager_util.h b/components/password_manager/core/browser/password_manager_util.h
index 9f4331e..6e90fd05 100644
--- a/components/password_manager/core/browser/password_manager_util.h
+++ b/components/password_manager/core/browser/password_manager_util.h
@@ -12,6 +12,7 @@
 #include "base/callback.h"
 #include "base/strings/string16.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
+#include "components/password_manager/core/browser/password_store.h"
 #include "ui/gfx/native_widget_types.h"
 
 namespace autofill {
@@ -27,7 +28,6 @@
 namespace password_manager {
 class PasswordManagerDriver;
 class PasswordManagerClient;
-class PasswordStore;
 }
 
 namespace syncer {
@@ -137,6 +137,14 @@
     const autofill::PasswordForm& submitted_form,
     const std::map<base::string16, const autofill::PasswordForm*>& credentials);
 
+// This method creates a blacklisted form with |digests|'s scheme, signon_realm
+// and origin. This is done to avoid storing PII and to have a normalized unique
+// key. Furthermore it attempts to normalize the origin by stripping path
+// components. In case this fails (e.g. for non-standard origins like Android
+// credentials), the original origin is kept.
+autofill::PasswordForm MakeNormalizedBlacklistedForm(
+    password_manager::PasswordStore::FormDigest digest);
+
 }  // namespace password_manager_util
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_MANAGER_UTIL_H_
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc
index 6280fa6..f67f80f1 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -30,15 +30,18 @@
 
 constexpr char kTestAndroidRealm[] = "android://hash@com.example.beta.android";
 constexpr char kTestFederationURL[] = "https://google.com/";
+constexpr char kTestProxyOrigin[] = "http://proxy.com/";
+constexpr char kTestProxySignonRealm[] = "proxy.com/realm";
 constexpr char kTestURL[] = "https://example.com/login/";
 constexpr char kTestUsername[] = "Username";
 constexpr char kTestUsername2[] = "Username2";
 constexpr char kTestPassword[] = "12345";
 
-autofill::PasswordForm GetTestAndroidCredentials(const char* signon_realm) {
+autofill::PasswordForm GetTestAndroidCredential() {
   autofill::PasswordForm form;
   form.scheme = autofill::PasswordForm::SCHEME_HTML;
-  form.signon_realm = signon_realm;
+  form.origin = GURL(kTestAndroidRealm);
+  form.signon_realm = kTestAndroidRealm;
   form.username_value = base::ASCIIToUTF16(kTestUsername);
   form.password_value = base::ASCIIToUTF16(kTestPassword);
   return form;
@@ -54,6 +57,16 @@
   return form;
 }
 
+autofill::PasswordForm GetTestProxyCredential() {
+  autofill::PasswordForm form;
+  form.scheme = autofill::PasswordForm::SCHEME_BASIC;
+  form.origin = GURL(kTestProxyOrigin);
+  form.signon_realm = kTestProxySignonRealm;
+  form.username_value = base::ASCIIToUTF16(kTestUsername);
+  form.password_value = base::ASCIIToUTF16(kTestPassword);
+  return form;
+}
+
 std::map<base::string16, const autofill::PasswordForm*> MapFromCredentials(
     const std::vector<const autofill::PasswordForm*>& forms) {
   std::map<base::string16, const autofill::PasswordForm*> result;
@@ -74,10 +87,10 @@
 TEST(PasswordManagerUtil, TrimUsernameOnlyCredentials) {
   std::vector<std::unique_ptr<autofill::PasswordForm>> forms;
   std::vector<std::unique_ptr<autofill::PasswordForm>> expected_forms;
-  forms.push_back(std::make_unique<autofill::PasswordForm>(
-      GetTestAndroidCredentials(kTestAndroidRealm)));
-  expected_forms.push_back(std::make_unique<autofill::PasswordForm>(
-      GetTestAndroidCredentials(kTestAndroidRealm)));
+  forms.push_back(
+      std::make_unique<autofill::PasswordForm>(GetTestAndroidCredential()));
+  expected_forms.push_back(
+      std::make_unique<autofill::PasswordForm>(GetTestAndroidCredential()));
 
   autofill::PasswordForm username_only;
   username_only.scheme = autofill::PasswordForm::SCHEME_USERNAME_ONLY;
@@ -353,4 +366,32 @@
                 parsed, MapFromCredentials({&stored3, &stored2, &stored1})));
 }
 
+TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Android) {
+  autofill::PasswordForm blacklisted_credential = MakeNormalizedBlacklistedForm(
+      password_manager::PasswordStore::FormDigest(GetTestAndroidCredential()));
+  EXPECT_TRUE(blacklisted_credential.blacklisted_by_user);
+  EXPECT_EQ(PasswordForm::SCHEME_HTML, blacklisted_credential.scheme);
+  EXPECT_EQ(kTestAndroidRealm, blacklisted_credential.signon_realm);
+  EXPECT_EQ(GURL(kTestAndroidRealm), blacklisted_credential.origin);
+}
+
+TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Html) {
+  autofill::PasswordForm blacklisted_credential = MakeNormalizedBlacklistedForm(
+      password_manager::PasswordStore::FormDigest(GetTestCredential()));
+  EXPECT_TRUE(blacklisted_credential.blacklisted_by_user);
+  EXPECT_EQ(PasswordForm::SCHEME_HTML, blacklisted_credential.scheme);
+  EXPECT_EQ(GURL(kTestURL).GetOrigin().spec(),
+            blacklisted_credential.signon_realm);
+  EXPECT_EQ(GURL(kTestURL).GetOrigin(), blacklisted_credential.origin);
+}
+
+TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Proxy) {
+  autofill::PasswordForm blacklisted_credential = MakeNormalizedBlacklistedForm(
+      password_manager::PasswordStore::FormDigest(GetTestProxyCredential()));
+  EXPECT_TRUE(blacklisted_credential.blacklisted_by_user);
+  EXPECT_EQ(PasswordForm::SCHEME_BASIC, blacklisted_credential.scheme);
+  EXPECT_EQ(kTestProxySignonRealm, blacklisted_credential.signon_realm);
+  EXPECT_EQ(GURL(kTestProxyOrigin), blacklisted_credential.origin);
+}
+
 }  // namespace password_manager_util
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index 0a276b5..7f4e0a8 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -252,6 +252,11 @@
       base::BindOnce(&PasswordStore::GetAutofillableLoginsImpl, this));
 }
 
+void PasswordStore::GetBlacklistLogins(PasswordStoreConsumer* consumer) {
+  PostLoginsTaskAndReplyToConsumerWithResult(
+      consumer, base::BindOnce(&PasswordStore::GetBlacklistLoginsImpl, this));
+}
+
 void PasswordStore::GetAllLogins(PasswordStoreConsumer* consumer) {
   PostLoginsTaskAndReplyToConsumerWithResult(
       consumer, base::BindOnce(&PasswordStore::GetAllLoginsImpl, this));
@@ -794,6 +799,15 @@
   return obtained_forms;
 }
 
+std::vector<std::unique_ptr<PasswordForm>>
+PasswordStore::GetBlacklistLoginsImpl() {
+  DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
+  std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
+  if (!FillBlacklistLogins(&obtained_forms))
+    obtained_forms.clear();
+  return obtained_forms;
+}
+
 std::vector<std::unique_ptr<PasswordForm>> PasswordStore::GetAllLoginsImpl() {
   DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
   std::vector<std::unique_ptr<PasswordForm>> results;
diff --git a/components/password_manager/core/browser/password_store.h b/components/password_manager/core/browser/password_store.h
index 92bbb85..bd1c28f 100644
--- a/components/password_manager/core/browser/password_store.h
+++ b/components/password_manager/core/browser/password_store.h
@@ -189,6 +189,11 @@
   // The request will be cancelled if the consumer is destroyed.
   virtual void GetAutofillableLogins(PasswordStoreConsumer* consumer);
 
+  // Gets the complete list of PasswordForms that are blacklist entries and
+  // notifies |consumer| on completion. The request will be cancelled if the
+  // consumer is destroyed.
+  virtual void GetBlacklistLogins(PasswordStoreConsumer* consumer);
+
   // Gets the complete list of PasswordForms (regardless of their blacklist
   // status) and notify |consumer| on completion. The request will be cancelled
   // if the consumer is destroyed.
@@ -554,6 +559,9 @@
   std::vector<std::unique_ptr<autofill::PasswordForm>>
   GetAutofillableLoginsImpl();
 
+  // Finds all blacklist PasswordForms and returns the result.
+  std::vector<std::unique_ptr<autofill::PasswordForm>> GetBlacklistLoginsImpl();
+
   // Finds all PasswordForms and returns the result.
   std::vector<std::unique_ptr<autofill::PasswordForm>> GetAllLoginsImpl();
 
diff --git a/components/password_manager/core/browser/test_password_store.cc b/components/password_manager/core/browser/test_password_store.cc
index 5efa077..b73ec985 100644
--- a/components/password_manager/core/browser/test_password_store.cc
+++ b/components/password_manager/core/browser/test_password_store.cc
@@ -50,8 +50,22 @@
 PasswordStoreChangeList TestPasswordStore::AddLoginImpl(
     const autofill::PasswordForm& form) {
   PasswordStoreChangeList changes;
-  stored_passwords_[form.signon_realm].push_back(form);
-  changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
+  auto& passwords_for_signon_realm = stored_passwords_[form.signon_realm];
+  auto iter = std::find_if(
+      passwords_for_signon_realm.begin(), passwords_for_signon_realm.end(),
+      [&form](const auto& password) {
+        return ArePasswordFormUniqueKeyEqual(form, password);
+      });
+
+  if (iter != passwords_for_signon_realm.end()) {
+    changes.emplace_back(PasswordStoreChange::REMOVE, *iter);
+    changes.emplace_back(PasswordStoreChange::ADD, form);
+    *iter = form;
+    return changes;
+  }
+
+  changes.emplace_back(PasswordStoreChange::ADD, form);
+  passwords_for_signon_realm.push_back(form);
   return changes;
 }
 
diff --git a/components/password_manager/core/common/password_manager_pref_names.cc b/components/password_manager/core/common/password_manager_pref_names.cc
index 183f3d4..36fccca 100644
--- a/components/password_manager/core/common/password_manager_pref_names.cc
+++ b/components/password_manager/core/common/password_manager_pref_names.cc
@@ -8,6 +8,9 @@
 namespace password_manager {
 namespace prefs {
 
+const char kBlacklistedCredentialsNormalized[] =
+    "profile.blacklisted_credentials_normalized";
+
 const char kCredentialsEnableAutosignin[] = "credentials_enable_autosignin";
 const char kCredentialsEnableService[] = "credentials_enable_service";
 
diff --git a/components/password_manager/core/common/password_manager_pref_names.h b/components/password_manager/core/common/password_manager_pref_names.h
index 87df51ce..0eb4a653 100644
--- a/components/password_manager/core/common/password_manager_pref_names.h
+++ b/components/password_manager/core/common/password_manager_pref_names.h
@@ -13,6 +13,10 @@
 // Alphabetical list of preference names specific to the PasswordManager
 // component.
 
+// Boolean indicating whether blacklisted credentials in the password store
+// have already been normalized.
+extern const char kBlacklistedCredentialsNormalized[];
+
 // Boolean controlling whether the password manager allows automatic signing in
 // through Credential Manager API.
 extern const char kCredentialsEnableAutosignin[];
diff --git a/components/previews/content/previews_user_data.cc b/components/previews/content/previews_user_data.cc
index 3e0951d8..98cb43b 100644
--- a/components/previews/content/previews_user_data.cc
+++ b/components/previews/content/previews_user_data.cc
@@ -11,13 +11,15 @@
 const void* const kPreviewsUserDataKey = &kPreviewsUserDataKey;
 
 PreviewsUserData::PreviewsUserData(uint64_t page_id)
-    : page_id_(page_id), server_lite_page_info_(nullptr) {}
+    : page_id_(page_id),
+      random_coin_flip_for_navigation_(base::RandInt(0, 1)),
+      server_lite_page_info_(nullptr) {}
 
 PreviewsUserData::~PreviewsUserData() {}
 
 PreviewsUserData::PreviewsUserData(const PreviewsUserData& other)
     : page_id_(other.page_id_),
-      random_coin_flip_for_navigation_(base::RandInt(0, 1)),
+      random_coin_flip_for_navigation_(other.random_coin_flip_for_navigation_),
       navigation_ect_(other.navigation_ect_),
       data_savings_inflation_percent_(other.data_savings_inflation_percent_),
       cache_control_no_transform_directive_(
diff --git a/components/previews/content/previews_user_data_unittest.cc b/components/previews/content/previews_user_data_unittest.cc
index a4f486f..de9fe53 100644
--- a/components/previews/content/previews_user_data_unittest.cc
+++ b/components/previews/content/previews_user_data_unittest.cc
@@ -43,6 +43,7 @@
 
   PreviewsUserData data_copy(*data);
   EXPECT_EQ(id, data_copy.page_id());
+  EXPECT_EQ(data->CoinFlipForNavigation(), data_copy.CoinFlipForNavigation());
   EXPECT_EQ(123, data_copy.data_savings_inflation_percent());
   EXPECT_TRUE(data_copy.cache_control_no_transform_directive());
   EXPECT_EQ(previews::PreviewsType::NOSCRIPT,
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 032661653..ad72a3a 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -249,6 +249,7 @@
     "//gpu/command_buffer/client:raster",
     "//gpu/command_buffer/client:raster_interface",
     "//gpu/vulkan:buildflags",
+    "//mojo/public/cpp/base",
     "//mojo/public/cpp/system",
     "//third_party/libyuv",
     "//ui/gfx",
diff --git a/components/viz/common/DEPS b/components/viz/common/DEPS
index e8cea65c..2e907073 100644
--- a/components/viz/common/DEPS
+++ b/components/viz/common/DEPS
@@ -38,4 +38,8 @@
   "features.cc" : [
     "+gpu/config/gpu_finch_features.h",
   ],
+  "bitmap_allocation.cc" : [
+    # Only used to pass Mojo handles, not to communicate with the viz service.
+    "+mojo/public/cpp/base/shared_memory_utils.h",
+  ],
 }
diff --git a/components/viz/common/resources/bitmap_allocation.cc b/components/viz/common/resources/bitmap_allocation.cc
index d5ac586..ede0e1f3 100644
--- a/components/viz/common/resources/bitmap_allocation.cc
+++ b/components/viz/common/resources/bitmap_allocation.cc
@@ -7,11 +7,12 @@
 #include <stdint.h>
 
 #include "base/debug/alias.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/process/memory.h"
 #include "build/build_config.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/resource_sizes.h"
+#include "mojo/public/cpp/base/shared_memory_utils.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -40,9 +41,8 @@
 
 namespace bitmap_allocation {
 
-std::unique_ptr<base::SharedMemory> AllocateMappedBitmap(
-    const gfx::Size& size,
-    ResourceFormat format) {
+base::MappedReadOnlyRegion AllocateSharedBitmap(const gfx::Size& size,
+                                                ResourceFormat format) {
   DCHECK(IsBitmapFormatSupported(format));
   size_t bytes = 0;
   if (!ResourceSizes::MaybeSizeInBytes(size, format, &bytes)) {
@@ -50,62 +50,26 @@
     CollectMemoryUsageAndDie(size, format, std::numeric_limits<int>::max());
   }
 
-  auto mojo_buf = mojo::SharedBufferHandle::Create(bytes);
-  if (!mojo_buf->is_valid()) {
+  // NOTE: Need to use mojo::CreateReadOnlySharedMemoryRegion() instead of
+  // base::ReadOnlySharedMemoryRegion::Create() to ensure that this always work,
+  // even in sandboxed processes.
+  base::MappedReadOnlyRegion shm =
+      mojo::CreateReadOnlySharedMemoryRegion(bytes);
+  if (!shm.IsValid()) {
     DLOG(ERROR) << "Browser failed to allocate shared memory";
     CollectMemoryUsageAndDie(size, format, bytes);
   }
-
-  base::SharedMemoryHandle shared_buf;
-  if (mojo::UnwrapSharedMemoryHandle(std::move(mojo_buf), &shared_buf, nullptr,
-                                     nullptr) != MOJO_RESULT_OK) {
-    DLOG(ERROR) << "Browser failed to allocate shared memory";
-    CollectMemoryUsageAndDie(size, format, bytes);
-  }
-
-  auto memory = std::make_unique<base::SharedMemory>(shared_buf, false);
-  if (!memory->Map(bytes)) {
-    DLOG(ERROR) << "Browser failed to map shared memory";
-    CollectMemoryUsageAndDie(size, format, bytes);
-  }
-
-  return memory;
+  return shm;
 }
 
-mojo::ScopedSharedBufferHandle DuplicateAndCloseMappedBitmap(
-    base::SharedMemory* memory,
-    const gfx::Size& size,
-    ResourceFormat format) {
-  DCHECK(IsBitmapFormatSupported(format));
-  base::SharedMemoryHandle dupe_handle =
-      base::SharedMemory::DuplicateHandle(memory->handle());
-  if (!base::SharedMemory::IsHandleValid(dupe_handle)) {
-    DLOG(ERROR) << "Failed to duplicate shared memory handle for bitmap.";
-    CollectMemoryUsageAndDie(size, format, memory->requested_size());
-  }
-
-  memory->Close();
-
-  return mojo::WrapSharedMemoryHandle(
-      dupe_handle, memory->mapped_size(),
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
+mojo::ScopedSharedBufferHandle ToMojoHandle(
+    base::ReadOnlySharedMemoryRegion region) {
+  return mojo::WrapReadOnlySharedMemoryRegion(std::move(region));
 }
 
-mojo::ScopedSharedBufferHandle DuplicateWithoutClosingMappedBitmap(
-    const base::SharedMemory* memory,
-    const gfx::Size& size,
-    ResourceFormat format) {
-  DCHECK(IsBitmapFormatSupported(format));
-  base::SharedMemoryHandle dupe_handle =
-      base::SharedMemory::DuplicateHandle(memory->handle());
-  if (!base::SharedMemory::IsHandleValid(dupe_handle)) {
-    DLOG(ERROR) << "Failed to duplicate shared memory handle for bitmap.";
-    CollectMemoryUsageAndDie(size, format, memory->requested_size());
-  }
-
-  return mojo::WrapSharedMemoryHandle(
-      dupe_handle, memory->mapped_size(),
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
+base::ReadOnlySharedMemoryRegion FromMojoHandle(
+    mojo::ScopedSharedBufferHandle handle) {
+  return mojo::UnwrapReadOnlySharedMemoryRegion(std::move(handle));
 }
 
 }  // namespace bitmap_allocation
diff --git a/components/viz/common/resources/bitmap_allocation.h b/components/viz/common/resources/bitmap_allocation.h
index aa8c176..8900a16 100644
--- a/components/viz/common/resources/bitmap_allocation.h
+++ b/components/viz/common/resources/bitmap_allocation.h
@@ -7,14 +7,11 @@
 
 #include <memory>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/viz_common_export.h"
 #include "mojo/public/cpp/system/buffer.h"
 
-namespace base {
-class SharedMemory;
-}
-
 namespace gfx {
 class Size;
 }  // namespace gfx
@@ -23,36 +20,27 @@
 
 namespace bitmap_allocation {
 
-// Allocates a shared memory segment to hold |size| pixels in RGBA_8888
-// format. Crashes if allocation does not succeed. The returned SharedMemory
-// will be mapped.
-VIZ_COMMON_EXPORT std::unique_ptr<base::SharedMemory> AllocateMappedBitmap(
+// Allocates a read-only shared memory region and its writable mapping to hold
+// |size| pixels in specific |format|. Crashes if allocation does not succeed.
+VIZ_COMMON_EXPORT base::MappedReadOnlyRegion AllocateSharedBitmap(
     const gfx::Size& size,
     ResourceFormat format);
 
-// For a bitmap created with AllocateMappedBitmap(), this will duplicate the
-// handle to be passed to the display compositor, which can be in another
-// process. The handle must be duplicated because we want to close the handle
-// once it is mapped in this process. This will then close the original file
-// handle in |memory| to free it up to the operating system. Some platforms
-// have very limited namespaces for file handle counts and this avoids running
-// out of them. Pass the same |size| as to AllocateMappedBitmap(), for
-// debugging assistance.
-VIZ_COMMON_EXPORT mojo::ScopedSharedBufferHandle DuplicateAndCloseMappedBitmap(
-    base::SharedMemory* memory,
-    const gfx::Size& size,
-    ResourceFormat format);
+// Converts a base::ReadOnlySharedMemoryRegion to its corresponding
+// Mojo scoped handle. This simply calls mojo::WrapReadOnlySharedMemoryRegion()
+// but allows the caller to not include the corresponding header where it is
+// defined. Moreover, it will be easy to grep for all uses of this method
+// in the future when MojoHandles will not longer be necessary.
+// TODO(crbug.com/951391): Remove once refactor is completed.
+VIZ_COMMON_EXPORT mojo::ScopedSharedBufferHandle ToMojoHandle(
+    base::ReadOnlySharedMemoryRegion region);
 
-// Similar to DuplicateAndCloseMappedBitmap(), but to be used in cases where the
-// SharedMemory will have to be duplicated more than once. In that case the
-// handle must be kept valid, so it can not be closed. Beware this will keep an
-// open file handle on posix systems, which may contribute to surpassing handle
-// limits.
-VIZ_COMMON_EXPORT
-mojo::ScopedSharedBufferHandle DuplicateWithoutClosingMappedBitmap(
-    const base::SharedMemory* memory,
-    const gfx::Size& size,
-    ResourceFormat format);
+// Converts a scoped Mojo handle back to a base::ReadOnlySharedMemoryRegion
+// This simply calls mojo::UnwrapReadOnlySharedMemoryRegion(), but has the same
+// benefits as ToMojoHandle() described above.
+// TODO(crbug.com/951391): Remove once refactor is completed.
+VIZ_COMMON_EXPORT base::ReadOnlySharedMemoryRegion FromMojoHandle(
+    mojo::ScopedSharedBufferHandle handle);
 
 }  // namespace bitmap_allocation
 
diff --git a/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.cc b/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.cc
index e991f18..46d1cba 100644
--- a/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.cc
+++ b/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.cc
@@ -94,8 +94,8 @@
 
 FuzzedBitmap::FuzzedBitmap(SharedBitmapId id,
                            gfx::Size size,
-                           std::unique_ptr<base::SharedMemory> shared_memory)
-    : id(id), size(size), shared_memory(std::move(shared_memory)) {}
+                           base::ReadOnlySharedMemoryRegion shared_region)
+    : id(id), size(size), shared_region(std::move(shared_region)) {}
 FuzzedBitmap::~FuzzedBitmap() = default;
 FuzzedBitmap::FuzzedBitmap(FuzzedBitmap&& other) noexcept = default;
 
@@ -346,16 +346,16 @@
     const gfx::Size& size,
     SkColor color) {
   SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
-  std::unique_ptr<base::SharedMemory> shared_memory =
-      bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888);
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(size, RGBA_8888);
 
   SkBitmap bitmap;
   SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
-  bitmap.installPixels(info, shared_memory->memory(), info.minRowBytes());
+  bitmap.installPixels(info, shm.mapping.memory(), info.minRowBytes());
   bitmap.eraseColor(color);
 
   data_.allocated_bitmaps.push_back(
-      {shared_bitmap_id, size, std::move(shared_memory)});
+      {shared_bitmap_id, size, std::move(shm.region)});
 
   return &data_.allocated_bitmaps.back();
 }
diff --git a/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.h b/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.h
index 3d16f41..b029293 100644
--- a/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.h
+++ b/components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer_util.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include <vector>
 
-#include "base/memory/shared_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/service/compositor_frame_fuzzer/compositor_frame_fuzzer.pb.h"
@@ -18,7 +18,7 @@
 struct FuzzedBitmap {
   FuzzedBitmap(SharedBitmapId id,
                gfx::Size size,
-               std::unique_ptr<base::SharedMemory> shared_memory);
+               base::ReadOnlySharedMemoryRegion shared_region);
   ~FuzzedBitmap();
 
   FuzzedBitmap(FuzzedBitmap&& other) noexcept;
@@ -26,10 +26,7 @@
 
   SharedBitmapId id;
   gfx::Size size;
-
-  // TODO(kylechar): base::SharedMemory is deprecated, change to
-  // base::WritableSharedMemoryRegion.
-  std::unique_ptr<base::SharedMemory> shared_memory;
+  base::ReadOnlySharedMemoryRegion shared_region;
 
   DISALLOW_COPY(FuzzedBitmap);
 };
diff --git a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
index 8546b29..d084b0b4 100644
--- a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
+++ b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/surface_draw_quad.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/surfaces/surface_range.h"
 
 namespace viz {
@@ -57,11 +58,10 @@
                                                 sink_client.BindInterfacePtr());
 
   for (auto& fuzzed_bitmap : allocated_bitmaps) {
-    mojo::ScopedSharedBufferHandle handle =
-        bitmap_allocation::DuplicateAndCloseMappedBitmap(
-            fuzzed_bitmap.shared_memory.get(), fuzzed_bitmap.size,
-            ResourceFormat::RGBA_8888);
-    sink_ptr->DidAllocateSharedBitmap(std::move(handle), fuzzed_bitmap.id);
+    sink_ptr->DidAllocateSharedBitmap(
+        bitmap_allocation::ToMojoHandle(
+            fuzzed_bitmap.shared_region.Duplicate()),
+        fuzzed_bitmap.id);
   }
 
   lsi_allocator_.GenerateId();
diff --git a/components/viz/service/display/display_resource_provider_unittest.cc b/components/viz/service/display/display_resource_provider_unittest.cc
index 97c57a4ce..603d896 100644
--- a/components/viz/service/display/display_resource_provider_unittest.cc
+++ b/components/viz/service/display/display_resource_provider_unittest.cc
@@ -18,8 +18,9 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/logging.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "build/build_config.h"
 #include "cc/test/render_pass_test_utils.h"
 #include "cc/test/resource_provider_test_utils.h"
@@ -70,14 +71,13 @@
                                                 uint32_t value) {
   SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
 
-  std::unique_ptr<base::SharedMemory> shm =
-      bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888);
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(size, RGBA_8888);
   manager->ChildAllocatedSharedBitmap(
-      bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size,
-                                                       RGBA_8888),
-      shared_bitmap_id);
+      bitmap_allocation::ToMojoHandle(std::move(shm.region)), shared_bitmap_id);
 
-  std::fill_n(static_cast<uint32_t*>(shm->memory()), size.GetArea(), value);
+  auto* memory = static_cast<uint32_t*>(shm.mapping.memory());
+  std::fill_n(memory, size.GetArea(), value);
   return shared_bitmap_id;
 }
 
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 35b28738..715489e 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -4,12 +4,15 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <algorithm>
 #include <memory>
 #include <tuple>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/memory/aligned_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "build/build_config.h"
 #include "cc/base/math_util.h"
 #include "cc/paint/paint_flags.h"
@@ -62,17 +65,15 @@
                          bitmap.computeByteSize());
 }
 
-std::unique_ptr<base::SharedMemory> AllocateAndRegisterSharedBitmapMemory(
+base::WritableSharedMemoryMapping AllocateAndRegisterSharedBitmapMemory(
     const SharedBitmapId& id,
     const gfx::Size& size,
     SharedBitmapManager* shared_bitmap_manager) {
-  std::unique_ptr<base::SharedMemory> shm =
-      bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888);
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(size, RGBA_8888);
   shared_bitmap_manager->ChildAllocatedSharedBitmap(
-      bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size,
-                                                       RGBA_8888),
-      id);
-  return shm;
+      bitmap_allocation::ToMojoHandle(std::move(shm.region)), id);
+  return std::move(shm.mapping);
 }
 
 void DeleteSharedImage(scoped_refptr<ContextProvider> context_provider,
@@ -247,7 +248,7 @@
         RGBA_8888, gfx::ColorSpace(), MakePixelSpan(pixels));
   } else {
     SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
-    std::unique_ptr<base::SharedMemory> shm =
+    base::WritableSharedMemoryMapping mapping =
         AllocateAndRegisterSharedBitmapMemory(shared_bitmap_id, rect.size(),
                                               shared_bitmap_manager);
     resource = child_resource_provider->ImportResource(
@@ -255,8 +256,8 @@
                                            RGBA_8888),
         SingleReleaseCallback::Create(base::DoNothing()));
 
-    for (int i = 0; i < rect.size().GetArea(); ++i)
-      static_cast<uint32_t*>(shm->memory())[i] = pixels[i];
+    auto span = mapping.GetMemoryAsSpan<uint32_t>(pixels.size());
+    std::copy(pixels.begin(), pixels.end(), span.begin());
   }
 
   // Return the mapped resource id.
@@ -308,7 +309,7 @@
         RGBA_8888, gfx::ColorSpace(), MakePixelSpan(pixels));
   } else {
     SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
-    std::unique_ptr<base::SharedMemory> shm =
+    base::WritableSharedMemoryMapping mapping =
         AllocateAndRegisterSharedBitmapMemory(shared_bitmap_id, rect.size(),
                                               shared_bitmap_manager);
     resource = child_resource_provider->ImportResource(
@@ -316,8 +317,8 @@
                                            RGBA_8888),
         SingleReleaseCallback::Create(base::DoNothing()));
 
-    for (int i = 0; i < rect.size().GetArea(); ++i)
-      static_cast<uint32_t*>(shm->memory())[i] = pixels[i];
+    auto span = mapping.GetMemoryAsSpan<uint32_t>(pixels.size());
+    std::copy(pixels.begin(), pixels.end(), span.begin());
   }
 
   // Return the mapped resource id.
diff --git a/components/viz/service/display/software_renderer_unittest.cc b/components/viz/service/display/software_renderer_unittest.cc
index 853ef75..ca867491 100644
--- a/components/viz/service/display/software_renderer_unittest.cc
+++ b/components/viz/service/display/software_renderer_unittest.cc
@@ -8,7 +8,8 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/run_loop.h"
 #include "cc/test/animation_test_common.h"
 #include "cc/test/fake_output_surface_client.h"
@@ -77,16 +78,15 @@
 
   ResourceId AllocateAndFillSoftwareResource(const gfx::Size& size,
                                              const SkBitmap& source) {
-    std::unique_ptr<base::SharedMemory> shm =
-        bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888);
+    base::MappedReadOnlyRegion shm =
+        bitmap_allocation::AllocateSharedBitmap(size, RGBA_8888);
     SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
-    source.readPixels(info, shm->memory(), info.minRowBytes(), 0, 0);
+    source.readPixels(info, shm.mapping.memory(), info.minRowBytes(), 0, 0);
 
     // Registers the SharedBitmapId in the display compositor.
     SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
     shared_bitmap_manager_->ChildAllocatedSharedBitmap(
-        bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size,
-                                                         RGBA_8888),
+        bitmap_allocation::ToMojoHandle(std::move(shm.region)),
         shared_bitmap_id);
 
     // Makes a resource id that refers to the registered SharedBitmapId.
diff --git a/components/viz/service/display_embedder/server_shared_bitmap_manager.cc b/components/viz/service/display_embedder/server_shared_bitmap_manager.cc
index 66af729..f8d3070 100644
--- a/components/viz/service/display_embedder/server_shared_bitmap_manager.cc
+++ b/components/viz/service/display_embedder/server_shared_bitmap_manager.cc
@@ -10,11 +10,14 @@
 
 #include "base/lazy_instance.h"
 #include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/process_memory_dump.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/resource_sizes.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "ui/gfx/geometry/size.h"
@@ -23,13 +26,18 @@
 
 class BitmapData : public base::RefCounted<BitmapData> {
  public:
-  explicit BitmapData(size_t buffer_size) : buffer_size(buffer_size) {}
-  std::unique_ptr<base::SharedMemory> memory;
-  size_t buffer_size;
+  explicit BitmapData(base::ReadOnlySharedMemoryMapping mapping)
+      : mapping_(std::move(mapping)) {}
+
+  size_t size() const { return mapping_.size(); }
+  const base::UnguessableToken& mapped_id() const { return mapping_.guid(); }
+  const void* memory() const { return mapping_.memory(); }
 
  private:
   friend class base::RefCounted<BitmapData>;
   ~BitmapData() {}
+
+  base::ReadOnlySharedMemoryMapping mapping_;
   DISALLOW_COPY_AND_ASSIGN(BitmapData);
 };
 
@@ -40,8 +48,12 @@
 // alive.
 class ServerSharedBitmap : public SharedBitmap {
  public:
+  // NOTE: bitmap_data->memory() is read-only but SharedBitmap expects a
+  // uint8_t* pointer, even though all instances returned by a
+  // SharedBitmapManager will be used read-only.
   explicit ServerSharedBitmap(scoped_refptr<BitmapData> bitmap_data)
-      : SharedBitmap(static_cast<uint8_t*>(bitmap_data->memory->memory())),
+      : SharedBitmap(
+            static_cast<uint8_t*>(const_cast<void*>(bitmap_data->memory()))),
         bitmap_data_(std::move(bitmap_data)) {}
 
   ~ServerSharedBitmap() override {
@@ -74,10 +86,10 @@
 
   size_t bitmap_size;
   if (!ResourceSizes::MaybeSizeInBytes(size, format, &bitmap_size) ||
-      bitmap_size > data->buffer_size)
+      bitmap_size > data->size())
     return nullptr;
 
-  if (!data->memory->memory()) {
+  if (!data->memory()) {
     return nullptr;
   }
 
@@ -91,7 +103,7 @@
   if (it == handle_map_.end())
     return {};
   BitmapData* data = it->second.get();
-  return data->memory->mapped_id();
+  return data->mapped_id();
 }
 
 bool ServerSharedBitmapManager::ChildAllocatedSharedBitmap(
@@ -103,25 +115,21 @@
   if (base::ContainsKey(handle_map_, id))
     return false;
 
-  base::SharedMemoryHandle memory_handle;
-  size_t buffer_size;
-  MojoResult result = mojo::UnwrapSharedMemoryHandle(
-      std::move(buffer), &memory_handle, &buffer_size, nullptr);
+  base::ReadOnlySharedMemoryRegion region =
+      bitmap_allocation::FromMojoHandle(std::move(buffer));
 
   // This function handles public API requests, so verify we unwrapped a shared
   // memory handle before trying to use the handle.
-  if (result != MOJO_RESULT_OK)
+  if (!region.IsValid())
     return false;
 
-  auto data = base::MakeRefCounted<BitmapData>(buffer_size);
-  data->memory = std::make_unique<base::SharedMemory>(memory_handle, false);
-  // Map the memory to get a pointer to it, then close it to free up the fd so
-  // it can be reused. This doesn't unmap the memory. Some OS have a very
-  // limited number of fds and this avoids consuming them all.
-  data->memory->Map(data->buffer_size);
-  data->memory->Close();
+  base::ReadOnlySharedMemoryMapping mapping = region.Map();
+  if (!mapping.IsValid())
+    return false;
 
-  handle_map_[id] = std::move(data);
+  handle_map_[id] = base::MakeRefCounted<BitmapData>(std::move(mapping));
+
+  // Note: |region| will be destroyed at scope exit, releasing the fd.
   return true;
 }
 
@@ -149,11 +157,11 @@
 
     dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                     base::trace_event::MemoryAllocatorDump::kUnitsBytes,
-                    data->buffer_size);
+                    data->size());
 
     // This GUID is the same returned by GetSharedBitmapTracingGUIDFromId() so
     // other components use a consistent GUID for a given SharedBitmapId.
-    base::UnguessableToken shared_memory_guid = data->memory->mapped_id();
+    base::UnguessableToken shared_memory_guid = data->mapped_id();
     DCHECK(!shared_memory_guid.is_empty());
     pmd->CreateSharedMemoryOwnershipEdge(dump->guid(), shared_memory_guid,
                                          0 /* importance*/);
diff --git a/components/viz/service/display_embedder/server_shared_bitmap_manager.h b/components/viz/service/display_embedder/server_shared_bitmap_manager.h
index 21a699ff..fc9b8733 100644
--- a/components/viz/service/display_embedder/server_shared_bitmap_manager.h
+++ b/components/viz/service/display_embedder/server_shared_bitmap_manager.h
@@ -10,8 +10,8 @@
 
 #include "base/compiler_specific.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/shared_memory.h"
 #include "base/trace_event/memory_dump_provider.h"
+#include "base/unguessable_token.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/viz_service_export.h"
diff --git a/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc b/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc
index 7afe388..f730e3f 100644
--- a/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc
+++ b/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 
+#include "base/containers/span.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/resource_sizes.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -27,19 +29,15 @@
 
 TEST_F(ServerSharedBitmapManagerTest, TestCreate) {
   gfx::Size bitmap_size(1, 1);
-  size_t size_in_bytes;
-  EXPECT_TRUE(
-      ResourceSizes::MaybeSizeInBytes(bitmap_size, RGBA_8888, &size_in_bytes));
-  std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
-  bitmap->CreateAndMapAnonymous(size_in_bytes);
-  memset(bitmap->memory(), 0xff, size_in_bytes);
-  SharedBitmapId id = SharedBitmap::GenerateId();
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(bitmap_size, RGBA_8888);
+  EXPECT_TRUE(shm.IsValid());
+  base::span<uint8_t> span = shm.mapping.GetMemoryAsSpan<uint8_t>();
+  std::fill(span.begin(), span.end(), 0xff);
 
-  base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
-  mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
-      handle, size_in_bytes,
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
-  manager()->ChildAllocatedSharedBitmap(std::move(buffer_handle), id);
+  SharedBitmapId id = SharedBitmap::GenerateId();
+  manager()->ChildAllocatedSharedBitmap(
+      bitmap_allocation::ToMojoHandle(std::move(shm.region)), id);
 
   std::unique_ptr<SharedBitmap> large_bitmap;
   large_bitmap =
@@ -65,7 +63,8 @@
   std::unique_ptr<SharedBitmap> shared_bitmap;
   shared_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, RGBA_8888, id);
   ASSERT_TRUE(shared_bitmap);
-  EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), 4), 0);
+  EXPECT_TRUE(
+      std::equal(span.begin(), span.begin() + 4, shared_bitmap->pixels()));
 
   std::unique_ptr<SharedBitmap> large_bitmap2;
   large_bitmap2 =
@@ -76,70 +75,58 @@
   shared_bitmap2 = manager()->GetSharedBitmapFromId(bitmap_size, RGBA_8888, id);
   EXPECT_TRUE(shared_bitmap2->pixels() == shared_bitmap->pixels());
   shared_bitmap2.reset();
-  EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
-            0);
+  EXPECT_TRUE(std::equal(span.begin(), span.end(), shared_bitmap->pixels()));
 
   manager()->ChildDeletedSharedBitmap(id);
 
-  memset(bitmap->memory(), 0, size_in_bytes);
+  std::fill(span.begin(), span.end(), 0);
 
-  EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
-            0);
-  bitmap.reset();
+  EXPECT_TRUE(std::equal(span.begin(), span.end(), shared_bitmap->pixels()));
   shared_bitmap.reset();
 }
 
 TEST_F(ServerSharedBitmapManagerTest, AddDuplicate) {
   gfx::Size bitmap_size(1, 1);
-  size_t size_in_bytes;
-  EXPECT_TRUE(
-      ResourceSizes::MaybeSizeInBytes(bitmap_size, RGBA_8888, &size_in_bytes));
-  std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
-  bitmap->CreateAndMapAnonymous(size_in_bytes);
-  memset(bitmap->memory(), 0xff, size_in_bytes);
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(bitmap_size, RGBA_8888);
+  EXPECT_TRUE(shm.IsValid());
+  base::span<uint8_t> span = shm.mapping.GetMemoryAsSpan<uint8_t>();
+  std::fill(span.begin(), span.end(), 0xff);
   SharedBitmapId id = SharedBitmap::GenerateId();
 
-  base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
-  mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
-      handle, size_in_bytes,
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
-  manager()->ChildAllocatedSharedBitmap(std::move(buffer_handle), id);
+  // NOTE: Duplicate the mapping to compare its content later.
+  manager()->ChildAllocatedSharedBitmap(
+      bitmap_allocation::ToMojoHandle(std::move(shm.region)), id);
 
-  std::unique_ptr<base::SharedMemory> bitmap2(new base::SharedMemory());
-  bitmap2->CreateAndMapAnonymous(size_in_bytes);
-  memset(bitmap2->memory(), 0x00, size_in_bytes);
+  base::MappedReadOnlyRegion shm2 =
+      bitmap_allocation::AllocateSharedBitmap(bitmap_size, RGBA_8888);
+  EXPECT_TRUE(shm2.IsValid());
+  base::span<uint8_t> span2 = shm.mapping.GetMemoryAsSpan<uint8_t>();
+  std::fill(span2.begin(), span2.end(), 0x00);
 
-  base::SharedMemoryHandle handle2 = bitmap->handle().Duplicate();
-  buffer_handle = mojo::WrapSharedMemoryHandle(
-      handle2, size_in_bytes,
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
-  manager()->ChildAllocatedSharedBitmap(std::move(buffer_handle), id);
+  manager()->ChildAllocatedSharedBitmap(
+      bitmap_allocation::ToMojoHandle(std::move(shm2.region)), id);
 
   std::unique_ptr<SharedBitmap> shared_bitmap;
   shared_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, RGBA_8888, id);
   ASSERT_TRUE(shared_bitmap);
-  EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
-            0);
+  EXPECT_TRUE(std::equal(span.begin(), span.end(), shared_bitmap->pixels()));
   manager()->ChildDeletedSharedBitmap(id);
 }
 
 TEST_F(ServerSharedBitmapManagerTest, SharedMemoryHandle) {
   gfx::Size bitmap_size(1, 1);
-  size_t size_in_bytes;
-  EXPECT_TRUE(
-      ResourceSizes::MaybeSizeInBytes(bitmap_size, RGBA_8888, &size_in_bytes));
-  std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
-  bitmap->CreateAndMapAnonymous(size_in_bytes);
-  memset(bitmap->memory(), 0xff, size_in_bytes);
-  base::UnguessableToken shared_memory_guid = bitmap->handle().GetGUID();
+  base::MappedReadOnlyRegion shm =
+      bitmap_allocation::AllocateSharedBitmap(bitmap_size, RGBA_8888);
+  EXPECT_TRUE(shm.IsValid());
+  base::span<uint8_t> span = shm.mapping.GetMemoryAsSpan<uint8_t>();
+  std::fill(span.begin(), span.end(), 0xff);
+  base::UnguessableToken shared_memory_guid = shm.mapping.guid();
   EXPECT_FALSE(shared_memory_guid.is_empty());
 
   SharedBitmapId id = SharedBitmap::GenerateId();
-  base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
-  mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
-      handle, size_in_bytes,
-      mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
-  manager()->ChildAllocatedSharedBitmap(std::move(buffer_handle), id);
+  manager()->ChildAllocatedSharedBitmap(
+      bitmap_allocation::ToMojoHandle(std::move(shm.region)), id);
 
   base::UnguessableToken tracing_guid =
       manager()->GetSharedBitmapTracingGUIDFromId(id);
diff --git a/components/viz/test/test_shared_bitmap_manager.cc b/components/viz/test/test_shared_bitmap_manager.cc
index a173281..7111dd9 100644
--- a/components/viz/test/test_shared_bitmap_manager.cc
+++ b/components/viz/test/test_shared_bitmap_manager.cc
@@ -6,7 +6,8 @@
 
 #include <stdint.h>
 
-#include "base/memory/shared_memory.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
@@ -24,18 +25,23 @@
     const SharedBitmapId& id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (bitmap_map_.find(id) == bitmap_map_.end())
+  const auto it = mapping_map_.find(id);
+  if (it == mapping_map_.end())
     return nullptr;
-  uint8_t* pixels = static_cast<uint8_t*>(bitmap_map_[id]->memory());
+  // NOTE: pixels needs to be writable for legacy reasons, but SharedBitmap
+  // instances returned by a SharedBitmapManager are always read-only.
+  uint8_t* pixels =
+      static_cast<uint8_t*>(const_cast<void*>(it->second.memory()));
   return std::make_unique<SharedBitmap>(pixels);
 }
 
 base::UnguessableToken
 TestSharedBitmapManager::GetSharedBitmapTracingGUIDFromId(
     const SharedBitmapId& id) {
-  if (bitmap_map_.find(id) == bitmap_map_.end())
+  const auto it = mapping_map_.find(id);
+  if (it == mapping_map_.end())
     return {};
-  return bitmap_map_[id]->mapped_id();
+  return it->second.guid();
 }
 
 bool TestSharedBitmapManager::ChildAllocatedSharedBitmap(
@@ -46,19 +52,13 @@
   // TestSharedBitmapManager is both the client and service side. So the
   // notification here should be about a bitmap that was previously allocated
   // with AllocateSharedBitmap().
-  if (bitmap_map_.find(id) == bitmap_map_.end()) {
-    base::SharedMemoryHandle memory_handle;
-    size_t buffer_size;
-    MojoResult result = mojo::UnwrapSharedMemoryHandle(
-        std::move(buffer), &memory_handle, &buffer_size, nullptr);
-    DCHECK_EQ(result, MOJO_RESULT_OK);
-    auto memory = std::make_unique<base::SharedMemory>(memory_handle, false);
-    bool mapped = memory->Map(buffer_size);
-    DCHECK(mapped);
-    memory->Close();
-
-    bitmap_map_.emplace(id, memory.get());
-    owned_map_.emplace(id, std::move(memory));
+  if (mapping_map_.find(id) == mapping_map_.end()) {
+    base::ReadOnlySharedMemoryRegion region =
+        bitmap_allocation::FromMojoHandle(std::move(buffer));
+    DCHECK(region.IsValid());
+    base::ReadOnlySharedMemoryMapping mapping = region.Map();
+    DCHECK(mapping.IsValid());
+    mapping_map_.emplace(id, std::move(mapping));
   }
 
   // The same bitmap id should not be notified more than once.
@@ -72,8 +72,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   notified_set_.erase(id);
-  bitmap_map_.erase(id);
-  owned_map_.erase(id);
+  mapping_map_.erase(id);
 }
 
 }  // namespace viz
diff --git a/components/viz/test/test_shared_bitmap_manager.h b/components/viz/test/test_shared_bitmap_manager.h
index e46da32f..cacc75d 100644
--- a/components/viz/test/test_shared_bitmap_manager.h
+++ b/components/viz/test/test_shared_bitmap_manager.h
@@ -8,13 +8,10 @@
 #include <map>
 #include <set>
 
+#include "base/memory/shared_memory_mapping.h"
 #include "base/sequence_checker.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 
-namespace base {
-class SharedMemory;
-}  // namespace base
-
 namespace viz {
 
 class TestSharedBitmapManager : public SharedBitmapManager {
@@ -36,8 +33,7 @@
  private:
   SEQUENCE_CHECKER(sequence_checker_);
 
-  std::map<SharedBitmapId, base::SharedMemory*> bitmap_map_;
-  std::map<SharedBitmapId, std::unique_ptr<base::SharedMemory>> owned_map_;
+  std::map<SharedBitmapId, base::ReadOnlySharedMemoryMapping> mapping_map_;
   std::set<SharedBitmapId> notified_set_;
 };
 
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 04e2c388..8a2ae78 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -3799,7 +3799,7 @@
 
 // Verify WebUI download will success with an associated renderer process.
 IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadFromWebUI) {
-  GURL webui_url("chrome://resources/images/apps/blue_button.png");
+  GURL webui_url(GetWebUIURL("resources/images/apps/blue_button.png"));
   NavigateToURL(shell(), webui_url);
   SetupEnsureNoPendingDownloads();
 
diff --git a/content/browser/fileapi/file_system_manager_impl.cc b/content/browser/fileapi/file_system_manager_impl.cc
index 59e6f8f..3c9125c 100644
--- a/content/browser/fileapi/file_system_manager_impl.cc
+++ b/content/browser/fileapi/file_system_manager_impl.cc
@@ -124,17 +124,17 @@
 FileSystemManagerImpl::FileSystemManagerImpl(
     int process_id,
     int frame_id,
-    storage::FileSystemContext* file_system_context,
+    scoped_refptr<storage::FileSystemContext> file_system_context,
     scoped_refptr<ChromeBlobStorageContext> blob_storage_context)
     : process_id_(process_id),
       frame_id_(frame_id),
-      context_(file_system_context),
+      context_(std::move(file_system_context)),
       security_policy_(ChildProcessSecurityPolicyImpl::GetInstance()),
-      blob_storage_context_(blob_storage_context),
+      blob_storage_context_(std::move(blob_storage_context)),
       weak_factory_(this) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(context_);
-  DCHECK(blob_storage_context);
+  DCHECK(blob_storage_context_);
   bindings_.set_connection_error_handler(base::BindRepeating(
       &FileSystemManagerImpl::OnConnectionError, base::Unretained(this)));
 }
@@ -566,8 +566,7 @@
   context_->default_file_task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&FileSystemManagerImpl::GetPlatformPathOnFileThread, path,
-                     process_id_, base::Unretained(context_), GetWeakPtr(),
-                     std::move(callback)));
+                     process_id_, context_, GetWeakPtr(), std::move(callback)));
 }
 
 void FileSystemManagerImpl::CreateWriter(const GURL& file_path,
@@ -831,13 +830,13 @@
 void FileSystemManagerImpl::GetPlatformPathOnFileThread(
     const GURL& path,
     int process_id,
-    storage::FileSystemContext* context,
+    scoped_refptr<storage::FileSystemContext> context,
     base::WeakPtr<FileSystemManagerImpl> file_system_manager,
     GetPlatformPathCallback callback) {
   DCHECK(context->default_file_task_runner()->RunsTasksInCurrentSequence());
 
   SyncGetPlatformPath(
-      context, process_id, path,
+      context.get(), process_id, path,
       base::BindOnce(
           [](base::WeakPtr<FileSystemManagerImpl> file_system_manager,
              GetPlatformPathCallback callback,
@@ -854,7 +853,7 @@
 base::Optional<base::File::Error> FileSystemManagerImpl::ValidateFileSystemURL(
     const storage::FileSystemURL& url) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!FileSystemURLIsValid(context_, url))
+  if (!FileSystemURLIsValid(context_.get(), url))
     return base::File::FILE_ERROR_INVALID_URL;
 
   // Deny access to files in PluginPrivate FileSystem from JavaScript.
diff --git a/content/browser/fileapi/file_system_manager_impl.h b/content/browser/fileapi/file_system_manager_impl.h
index 12b7cf8..5055e50 100644
--- a/content/browser/fileapi/file_system_manager_impl.h
+++ b/content/browser/fileapi/file_system_manager_impl.h
@@ -63,7 +63,7 @@
   FileSystemManagerImpl(
       int process_id,
       int frame_id,
-      storage::FileSystemContext* file_system_context,
+      scoped_refptr<storage::FileSystemContext> file_system_context,
       scoped_refptr<ChromeBlobStorageContext> blob_storage_context);
   ~FileSystemManagerImpl() override;
   base::WeakPtr<FileSystemManagerImpl> GetWeakPtr();
@@ -193,7 +193,7 @@
   static void GetPlatformPathOnFileThread(
       const GURL& path,
       int process_id,
-      storage::FileSystemContext* context,
+      scoped_refptr<storage::FileSystemContext> context,
       base::WeakPtr<FileSystemManagerImpl> file_system_manager,
       GetPlatformPathCallback callback);
   // Returns an error if |url| is invalid.
@@ -213,7 +213,7 @@
 
   const int process_id_;
   const int frame_id_;
-  storage::FileSystemContext* const context_;
+  const scoped_refptr<storage::FileSystemContext> context_;
   ChildProcessSecurityPolicyImpl* const security_policy_;
   const scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
   std::unique_ptr<storage::FileSystemOperationRunner> operation_runner_;
diff --git a/content/browser/frame_host/navigation_handle_impl_browsertest.cc b/content/browser/frame_host/navigation_handle_impl_browsertest.cc
index 01971ad..26a3c591 100644
--- a/content/browser/frame_host/navigation_handle_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_handle_impl_browsertest.cc
@@ -1924,7 +1924,7 @@
 // the privileged process.
 IN_PROC_BROWSER_TEST_F(NavigationHandleImplBrowserTest,
                        BlockedRequestAfterWebUI) {
-  GURL web_ui_url("chrome://gpu");
+  GURL web_ui_url(GetWebUIURL("gpu"));
   WebContents* web_contents = shell()->web_contents();
 
   // Navigate to the initial page.
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
index 93ce013e7..abd8192 100644
--- a/content/browser/navigation_browsertest.cc
+++ b/content/browser/navigation_browsertest.cc
@@ -1770,7 +1770,7 @@
   notification_registrar.Add(&crash_observer,
                              content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
                              content::NotificationService::AllSources());
-  NavigateToURLBlockUntilNavigationsComplete(web_contents, GURL("chrome:crash"),
+  NavigateToURLBlockUntilNavigationsComplete(web_contents, GetWebUIURL("crash"),
                                              1);
 
   // Wait for navigation in new WebContents to finish.
diff --git a/content/browser/network_service_browsertest.cc b/content/browser/network_service_browsertest.cc
index 0789ba39..190b319 100644
--- a/content/browser/network_service_browsertest.cc
+++ b/content/browser/network_service_browsertest.cc
@@ -214,7 +214,7 @@
 
 // Verifies that WebUI pages with WebUI bindings can't make network requests.
 IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, WebUIBindingsNoHttp) {
-  GURL test_url("chrome://webui/");
+  GURL test_url(GetWebUIURL("webui/"));
   NavigateToURL(shell(), test_url);
   RenderProcessKilledObserver killed_observer(shell()->web_contents());
   ASSERT_FALSE(CheckCanLoadHttp());
@@ -224,7 +224,7 @@
 
 // Verifies that WebUI pages without WebUI bindings can make network requests.
 IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, NoWebUIBindingsHttp) {
-  GURL test_url("chrome://webui/nobinding/");
+  GURL test_url(GetWebUIURL("webui/nobinding/"));
   NavigateToURL(shell(), test_url);
   ASSERT_TRUE(CheckCanLoadHttp());
 }
@@ -233,7 +233,7 @@
 // ChildProcessSecurityPolicyImpl::CanRequestURL is properly rejected.
 IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest,
                        FileSystemBindingsCorrectOrigin) {
-  GURL test_url("chrome://webui/nobinding/");
+  GURL test_url(GetWebUIURL("webui/nobinding/"));
   NavigateToURL(shell(), test_url);
 
   // Note: must be filesystem scheme (obviously).
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index db59da8..9b35d7f 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -5088,7 +5088,8 @@
   EXPECT_EQ(popup_url, popup->web_contents()->GetLastCommittedURL());
   EXPECT_TRUE(
       base::MatchPattern(console_delegate.message(),
-                         "Not allowed to load local resource: chrome:*"));
+                         std::string("Not allowed to load local resource: ") +
+                             kChromeUIScheme + ":*"));
 }
 
 // Verify that named frames are discoverable from their opener's ancestors.
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index b4b07f5..84ca6c56 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -552,7 +552,7 @@
 
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
                        AppendingFrameInWebUIDoesNotCrash) {
-  const GURL kWebUIUrl("chrome://tracing");
+  const GURL kWebUIUrl(GetWebUIURL("tracing"));
   const char kJSCodeForAppendingFrame[] =
       "document.body.appendChild(document.createElement('iframe'));";
 
@@ -873,7 +873,7 @@
 
   // Kill the renderer process so when the navigate again, it will be a fresh
   // renderer with an empty in-memory cache.
-  NavigateToURL(shell(), GURL("chrome:crash"));
+  NavigateToURL(shell(), GetWebUIURL("crash"));
 
   // Reload that URL, the subresource should be served from the network cache.
   before = base::TimeTicks::Now();
@@ -999,7 +999,7 @@
   // valid and return if the script was cached or not.
   bool TestResourceLoad(const GURL& url, const GURL& sub_frame) {
     // Kill the renderer to clear the in-memory cache.
-    NavigateToURL(shell(), GURL("chrome:crash"));
+    NavigateToURL(shell(), GetWebUIURL("crash"));
 
     // Observe network requests.
     ResourceLoadObserver observer(shell());
@@ -1200,7 +1200,7 @@
       observer.resource_load_infos()[1]->network_info->network_accessed);
   observer.Reset();
 
-  NavigateToURL(shell(), GURL("chrome://gpu"));
+  NavigateToURL(shell(), GetWebUIURL("gpu"));
   ASSERT_LE(1U, observer.resource_load_infos().size());
   for (const mojom::ResourceLoadInfoPtr& resource_load_info :
        observer.resource_load_infos()) {
@@ -1679,8 +1679,7 @@
 
 // Test that view source mode for a webui page can be opened.
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ViewSourceWebUI) {
-  const std::string kUrl =
-      "view-source:chrome://" + std::string(kChromeUIGpuHost);
+  const std::string kUrl = "view-source:" + GetWebUIURLString(kChromeUIGpuHost);
   const GURL kGURL(kUrl);
   NavigateToURL(shell(), kGURL);
   EXPECT_EQ(base::ASCIIToUTF16(kUrl), shell()->web_contents()->GetTitle());
diff --git a/content/browser/webui/web_ui_mojo_browsertest.cc b/content/browser/webui/web_ui_mojo_browsertest.cc
index f94314b8..a900571 100644
--- a/content/browser/webui/web_ui_mojo_browsertest.cc
+++ b/content/browser/webui/web_ui_mojo_browsertest.cc
@@ -34,6 +34,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "content/test/data/web_ui_test_mojo_bindings.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -264,10 +265,8 @@
   void NavigateWithNewWebUI(const std::string& path) {
     // Load a dummy WebUI URL first so that a new WebUI is set up when we load
     // the URL we're actually interested in.
-    EXPECT_TRUE(NavigateToURL(shell(), GURL("chrome://dummy-web-ui")));
-
-    constexpr char kChromeUIMojoWebUIOrigin[] = "chrome://mojo-web-ui/";
-    EXPECT_TRUE(NavigateToURL(shell(), GURL(kChromeUIMojoWebUIOrigin + path)));
+    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("dummy-web-ui")));
+    EXPECT_TRUE(NavigateToURL(shell(), GetWebUIURL("mojo-web-ui/" + path)));
   }
 
   // Run |script| and return a boolean result.
@@ -308,7 +307,7 @@
   g_got_message = false;
   base::RunLoop run_loop;
   factory()->set_run_loop(&run_loop);
-  GURL test_url("chrome://mojo-web-ui/web_ui_mojo.html?ping");
+  GURL test_url(GetWebUIURL("mojo-web-ui/web_ui_mojo.html?ping"));
   NavigateToURL(shell(), test_url);
   // RunLoop is quit when message received from page.
   run_loop.Run();
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.cc b/content/renderer/pepper/pepper_graphics_2d_host.cc
index f75a138..c793140 100644
--- a/content/renderer/pepper/pepper_graphics_2d_host.cc
+++ b/content/renderer/pepper/pepper_graphics_2d_host.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/numerics/checked_math.h"
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
@@ -741,15 +742,15 @@
   }
   if (!shared_bitmap) {
     viz::SharedBitmapId id = viz::SharedBitmap::GenerateId();
-    std::unique_ptr<base::SharedMemory> shm =
-        viz::bitmap_allocation::AllocateMappedBitmap(pixel_image_size,
+    base::MappedReadOnlyRegion shm =
+        viz::bitmap_allocation::AllocateSharedBitmap(pixel_image_size,
                                                      viz::RGBA_8888);
     shared_bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
         id, std::move(shm), pixel_image_size, viz::RGBA_8888);
     registration = bitmap_registrar->RegisterSharedBitmapId(id, shared_bitmap);
   }
   void* src = image_data_->Map();
-  memcpy(shared_bitmap->shared_memory()->memory(), src,
+  memcpy(shared_bitmap->memory(), src,
          viz::ResourceSizes::CheckedSizeInBytes<size_t>(pixel_image_size,
                                                         viz::RGBA_8888));
   image_data_->Unmap();
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 7b9c89c..19bcace 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -722,8 +722,7 @@
 
   // Verify that form posts to WebUI URLs will be sent to the browser process.
   auto form_navigation_info = std::make_unique<blink::WebNavigationInfo>();
-  form_navigation_info->url_request =
-      blink::WebURLRequest(GURL("chrome://foo"));
+  form_navigation_info->url_request = blink::WebURLRequest(GetWebUIURL("foo"));
   form_navigation_info->url_request.SetHttpMethod("POST");
   form_navigation_info->url_request.SetRequestorOrigin(requestor_origin);
   form_navigation_info->frame_type =
@@ -738,10 +737,9 @@
       FrameHostMsg_OpenURL::ID));
 
   // Verify that popup links to WebUI URLs also are sent to browser.
-  blink::WebURLRequest popup_request(GURL("chrome://foo"));
+  blink::WebURLRequest popup_request(GetWebUIURL("foo"));
   auto popup_navigation_info = std::make_unique<blink::WebNavigationInfo>();
-  popup_navigation_info->url_request =
-      blink::WebURLRequest(GURL("chrome://foo"));
+  popup_navigation_info->url_request = blink::WebURLRequest(GetWebUIURL("foo"));
   popup_navigation_info->url_request.SetRequestorOrigin(requestor_origin);
   popup_navigation_info->frame_type =
       network::mojom::RequestContextFrameType::kAuxiliary;
@@ -808,8 +806,7 @@
 
   // Navigations to WebUI URLs will also be sent to browser process.
   auto webui_navigation_info = std::make_unique<blink::WebNavigationInfo>();
-  webui_navigation_info->url_request =
-      blink::WebURLRequest(GURL("chrome://foo"));
+  webui_navigation_info->url_request = blink::WebURLRequest(GetWebUIURL("foo"));
   webui_navigation_info->url_request.SetRequestorOrigin(requestor_origin);
   webui_navigation_info->frame_type =
       network::mojom::RequestContextFrameType::kTopLevel;
diff --git a/content/shell/test_runner/test_plugin.cc b/content/shell/test_runner/test_plugin.cc
index cc1f3d5..38bee7f 100644
--- a/content/shell/test_runner/test_plugin.cc
+++ b/content/shell/test_runner/test_plugin.cc
@@ -12,7 +12,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/no_destructor.h"
 #include "base/strings/stringprintf.h"
 #include "cc/layers/texture_layer.h"
@@ -277,15 +277,15 @@
     shared_bitmap_ = nullptr;
   } else {
     viz::SharedBitmapId id = viz::SharedBitmap::GenerateId();
-    std::unique_ptr<base::SharedMemory> shm =
-        viz::bitmap_allocation::AllocateMappedBitmap(gfx::Rect(rect_).size(),
+    base::MappedReadOnlyRegion shm =
+        viz::bitmap_allocation::AllocateSharedBitmap(gfx::Rect(rect_).size(),
                                                      viz::RGBA_8888);
     shared_bitmap_ = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
         id, std::move(shm), gfx::Rect(rect_).size(), viz::RGBA_8888);
     // The |shared_bitmap_|'s id will be registered when being given to the
     // compositor.
 
-    DrawSceneSoftware(shared_bitmap_->shared_memory()->memory());
+    DrawSceneSoftware(shared_bitmap_->memory());
   }
 
   content_changed_ = true;
diff --git a/content/test/content_browser_test_test.cc b/content/test/content_browser_test_test.cc
index c40a840..372cb147 100644
--- a/content/test/content_browser_test_test.cc
+++ b/content/test/content_browser_test_test.cc
@@ -52,7 +52,7 @@
       shell()->web_contents()->GetMainFrame()->GetProcess(),
       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
 
-  NavigateToURL(shell(), GURL("chrome:crash"));
+  NavigateToURL(shell(), GetWebUIURL("crash"));
   renderer_shutdown_observer.Wait();
 
   EXPECT_FALSE(renderer_shutdown_observer.did_exit_normally());
diff --git a/content/test/gpu/gpu_tests/power_measurement_integration_test.py b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
index 06eb6d0..483706a 100644
--- a/content/test/gpu/gpu_tests/power_measurement_integration_test.py
+++ b/content/test/gpu/gpu_tests/power_measurement_integration_test.py
@@ -1,7 +1,6 @@
 # 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.
-
 """This script only works on Windows with Intel CPU. Intel Power Gadget needs
 to be installed on the machine before this script works. The software can be
 downloaded from:
@@ -39,17 +38,17 @@
 _POWER_MEASUREMENT_DELAY = 20
 
 # Measures power for [x] seconds and calculates the average as results.
-_POWER_MEASUREMENT_DURATION = 30
+_POWER_MEASUREMENT_DURATION = 15
 
 # Measures power in resolution of [x] milli-seconds.
 _POWER_MEASUREMENT_RESOLUTION = 100
 
 _GPU_RELATIVE_PATH = "content/test/data/gpu/"
 
-_DATA_PATHS = [os.path.join(
-                   path_util.GetChromiumSrcDir(), _GPU_RELATIVE_PATH),
-               os.path.join(
-                   path_util.GetChromiumSrcDir(), 'media', 'test', 'data')]
+_DATA_PATHS = [
+    os.path.join(path_util.GetChromiumSrcDir(), _GPU_RELATIVE_PATH),
+    os.path.join(path_util.GetChromiumSrcDir(), 'media', 'test', 'data')
+]
 
 _VIDEO_TEST_SCRIPT = r"""
   var _video_in_fullscreen = false;
@@ -203,6 +202,7 @@
   }
 """
 
+
 class PowerMeasurementIntegrationTest(gpu_integration_test.GpuIntegrationTest):
 
   _url_mode = None
@@ -214,47 +214,66 @@
   @classmethod
   def AddCommandlineArgs(cls, parser):
     super(PowerMeasurementIntegrationTest, cls).AddCommandlineArgs(parser)
-    parser.add_option("--duration", default=_POWER_MEASUREMENT_DURATION,
-                      type="int",
-                      help="specify how many seconds Intel Power Gadget "
-                      "measures. By default, %d seconds is selected." %
-                          _POWER_MEASUREMENT_DURATION)
-    parser.add_option("--delay", default=_POWER_MEASUREMENT_DELAY, type="int",
-                      help="specify how many seconds we skip in the data "
-                      "Intel Power Gadget collects. This time is for starting "
-                      "video play, switching to fullscreen mode, etc. "
-                      "By default, %d seconds is selected." %
-                          _POWER_MEASUREMENT_DELAY)
-    parser.add_option("--resolution", default=100, type="int",
-                      help="specify how often Intel Power Gadget samples "
-                      "data in milliseconds. By default, 100 ms is selected.")
-    parser.add_option("--url",
-                      help="specify the webpage URL the browser launches with.")
-    parser.add_option("--fullscreen", action="store_true", default=False,
-                      help="specify if the browser goes to fullscreen mode "
-                      "automatically, specifically if there is a single video "
-                      "element in the page, switch it to fullsrceen mode.")
-    parser.add_option("--underlay", action="store_true", default=False,
-                      help="add a layer on top so the video layer becomes an "
-                      "underlay.")
-    parser.add_option("--logdir",
-                      help="Speficy where the Intel Power Gadget log file "
-                      "should be stored. If specified, the log file name will "
-                      "include a timestamp. If not specified, the log file "
-                      "will be PowerLog.csv at the current dir and will be "
-                      "overwritten at next run.")
-    parser.add_option("--repeat", default=1, type="int",
-                      help="specify how many times to repreat the measurement. "
-                      "By default, measure only once. If measure more than "
-                      "once, between each measurement, browser restarts.")
-    parser.add_option("--outliers", default=0, type="int",
-                      help="if a test is repeated multiples and outliers is "
-                      "set to N, then N smallest results and N largest results "
-                      "are discarded before computing mean and stdev.")
-    parser.add_option("--bypass-ipg", action="store_true", default=False,
-                      help="Do not launch Intel Power Gadget. This is for "
-                      "testing convenience on machines where Intel Power "
-                      "Gadget does not work.")
+    parser.add_option(
+        "--duration",
+        default=_POWER_MEASUREMENT_DURATION,
+        type="int",
+        help="specify how many seconds Intel Power Gadget measures. By "
+        "default, %d seconds is selected." % _POWER_MEASUREMENT_DURATION)
+    parser.add_option(
+        "--delay",
+        default=_POWER_MEASUREMENT_DELAY,
+        type="int",
+        help="specify how many seconds we skip in the data Intel Power Gadget "
+        "collects. This time is for starting video play, switching to "
+        "fullscreen mode, etc. By default, %d seconds is selected." %
+        _POWER_MEASUREMENT_DELAY)
+    parser.add_option(
+        "--resolution",
+        default=100,
+        type="int",
+        help="specify how often Intel Power Gadget samples data in "
+        "milliseconds. By default, 100 ms is selected.")
+    parser.add_option(
+        "--url", help="specify the webpage URL the browser launches with.")
+    parser.add_option(
+        "--fullscreen",
+        action="store_true",
+        default=False,
+        help="specify if the browser goes to fullscreen mode automatically, "
+        "specifically if there is a single video element in the page, switch "
+        "it to fullsrceen mode.")
+    parser.add_option(
+        "--underlay",
+        action="store_true",
+        default=False,
+        help="add a layer on top so the video layer becomes an underlay.")
+    parser.add_option(
+        "--logdir",
+        help="Speficy where the Intel Power Gadget log file should be stored. "
+        "If specified, the log file name will include a timestamp. If not "
+        "specified, the log file will be PowerLog.csv at the current dir and "
+        "will be overwritten at next run.")
+    parser.add_option(
+        "--repeat",
+        default=3,
+        type="int",
+        help="specify how many times to repreat the measurement. By default, "
+        "measure only once. If measure more than once, between each "
+        "measurement, browser restarts.")
+    parser.add_option(
+        "--outliers",
+        default=0,
+        type="int",
+        help="if a test is repeated multiples and outliers is set to N, then "
+        "N smallest results and N largest results are discarded before "
+        "computing mean and stdev.")
+    parser.add_option(
+        "--bypass-ipg",
+        action="store_true",
+        default=False,
+        help="Do not launch Intel Power Gadget. This is for testing "
+        "convenience on machines where Intel Power Gadget does not work.")
 
   @classmethod
   def GenerateGpuTests(cls, options):
@@ -262,46 +281,57 @@
       # This is for local testing convenience only and is not to be added to
       # any bots.
       cls._url_mode = True
-      yield ('URL', options.url,
-             {'test_func': 'URL',
-              'repeat': options.repeat,
-              'outliers': options.outliers,
-              'fullscreen': options.fullscreen,
-              'underlay': options.underlay,
-              'logdir': options.logdir,
-              'duration': options.duration,
-              'delay': options.delay,
-              'resolution': options.resolution,
-              'bypass_ipg': options.bypass_ipg})
+      yield ('URL', options.url, {
+          'test_func': 'URL',
+          'repeat': options.repeat,
+          'outliers': options.outliers,
+          'fullscreen': options.fullscreen,
+          'underlay': options.underlay,
+          'logdir': options.logdir,
+          'duration': options.duration,
+          'delay': options.delay,
+          'resolution': options.resolution,
+          'bypass_ipg': options.bypass_ipg
+      })
     else:
       cls._url_mode = False
-      yield ('Basic', '-',
-             {'test_func': 'Basic',
-              'bypass_ipg': options.bypass_ipg})
+      yield ('Basic', '-', {
+          'test_func': 'Basic',
+          'repeat': options.repeat,
+          'bypass_ipg': options.bypass_ipg
+      })
       yield ('Video_720_MP4',
-             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
-             {'test_func': 'Video',
-              'bypass_ipg': options.bypass_ipg,
-              'underlay': False,
-              'fullscreen': False})
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html', {
+                 'test_func': 'Video',
+                 'repeat': options.repeat,
+                 'bypass_ipg': options.bypass_ipg,
+                 'underlay': False,
+                 'fullscreen': False
+             })
       yield ('Video_720_MP4_Underlay',
-             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
-             {'test_func': 'Video',
-              'bypass_ipg': options.bypass_ipg,
-              'underlay': True,
-              'fullscreen': False})
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html', {
+                 'test_func': 'Video',
+                 'repeat': options.repeat,
+                 'bypass_ipg': options.bypass_ipg,
+                 'underlay': True,
+                 'fullscreen': False
+             })
       yield ('Video_720_MP4_Fullscreen',
-             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
-             {'test_func': 'Video',
-              'bypass_ipg': options.bypass_ipg,
-              'underlay': False,
-              'fullscreen': True})
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html', {
+                 'test_func': 'Video',
+                 'repeat': options.repeat,
+                 'bypass_ipg': options.bypass_ipg,
+                 'underlay': False,
+                 'fullscreen': True
+             })
       yield ('Video_720_MP4_Underlay_Fullscreen',
-             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html',
-             {'test_func': 'Video',
-              'bypass_ipg': options.bypass_ipg,
-              'underlay': True,
-              'fullscreen': True})
+             _GPU_RELATIVE_PATH + 'power_video_bear_1280x720_mp4.html', {
+                 'test_func': 'Video',
+                 'repeat': options.repeat,
+                 'bypass_ipg': options.bypass_ipg,
+                 'underlay': True,
+                 'fullscreen': True
+             })
 
   @classmethod
   def SetUpProcess(cls):
@@ -330,23 +360,41 @@
     if bypass_ipg:
       logging.info("Bypassing Intel Power Gadget")
       time.sleep(total_time)
-      return
-    logfile = None # Use the default path
+      return {}
+    logfile = None  # Use the default path
     ipg_utils.RunIPG(total_time, _POWER_MEASUREMENT_RESOLUTION, logfile)
     results = ipg_utils.AnalyzeIPGLogFile(logfile, _POWER_MEASUREMENT_DELAY)
+    return results
+
+  @staticmethod
+  def _AppendResults(results_sum, results):
+    assert type(results_sum) is dict and type(results) is dict
+    assert results
+    first_append = not results_sum
+    for key, value in results.items():
+      if first_append:
+        results_sum[key] = [value]
+      else:
+        assert key in results_sum
+        assert type(results_sum[key]) is list
+        results_sum[key].append(value)
+    return results_sum
+
+  @staticmethod
+  def _LogResults(results):
     # TODO(zmo): output in a way that the results can be tracked at
     # chromeperf.appspot.com.
     logging.info("Results: %s", str(results))
 
   def _SetupVideo(self, fullscreen, underlay):
     self.tab.action_runner.WaitForJavaScriptCondition(
-      'waitForVideoToPlay()', timeout=30)
+        'waitForVideoToPlay()', timeout=30)
     if fullscreen:
       self.tab.action_runner.ExecuteJavaScript(
-        'startFullscreenMode();', user_gesture=True)
+          'startFullscreenMode();', user_gesture=True)
       try:
         self.tab.action_runner.WaitForJavaScriptCondition(
-          'isVideoInFullscreen()', timeout=5)
+            'isVideoInFullscreen()', timeout=5)
       except py_utils.TimeoutException:
         self.fail('requestFullscreen() fails to work, possibly because '
                   '|user_gesture| is not set.')
@@ -357,30 +405,49 @@
   # Actual test functions
 
   def _RunTest_Basic(self, test_path, params):
+    repeat = params['repeat']
     bypass_ipg = params['bypass_ipg']
-    PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
 
+    browser_args = PowerMeasurementIntegrationTest._AddDefaultArgs([])
+
+    results_sum = {}
+    for iteration in range(repeat):
+      logging.info('')
+      logging.info('Iteration #%d', iteration)
+      self.RestartBrowserWithArgs(browser_args)
+
+      results = PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
+      results_sum = PowerMeasurementIntegrationTest._AppendResults(
+          results_sum, results)
+    PowerMeasurementIntegrationTest._LogResults(results_sum)
 
   def _RunTest_Video(self, test_path, params):
+    repeat = params['repeat']
     fullscreen = params['fullscreen']
     underlay = params['underlay']
     bypass_ipg = params['bypass_ipg']
 
     disabled_features = [
-      'D3D11VideoDecoder',
-      'DirectCompositionUseNV12DecodeSwapChain',
-      'DirectCompositionUnderlays']
-    self.RestartBrowserWithArgs(
-      PowerMeasurementIntegrationTest._AddDefaultArgs([
-        '--disable-features=' + ','.join(disabled_features)]))
+        'D3D11VideoDecoder', 'DirectCompositionUseNV12DecodeSwapChain',
+        'DirectCompositionUnderlays'
+    ]
+    browser_args = PowerMeasurementIntegrationTest._AddDefaultArgs(
+        ['--disable-features=' + ','.join(disabled_features)])
 
-    url = self.UrlOfStaticFilePath(test_path)
-    self.tab.Navigate(
-      url, script_to_evaluate_on_commit=_VIDEO_TEST_SCRIPT)
-    self._SetupVideo(fullscreen=fullscreen, underlay=underlay)
+    results_sum = {}
+    for iteration in range(repeat):
+      logging.info('')
+      logging.info('Iteration #%d', iteration)
+      self.RestartBrowserWithArgs(browser_args)
 
-    PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
+      url = self.UrlOfStaticFilePath(test_path)
+      self.tab.Navigate(url, script_to_evaluate_on_commit=_VIDEO_TEST_SCRIPT)
+      self._SetupVideo(fullscreen=fullscreen, underlay=underlay)
 
+      results = PowerMeasurementIntegrationTest._MeasurePowerWithIPG(bypass_ipg)
+      results_sum = PowerMeasurementIntegrationTest._AppendResults(
+          results_sum, results)
+    PowerMeasurementIntegrationTest._LogResults(results_sum)
 
   def _RunTest_URL(self, test_path, params):
     repeat = params['repeat']
@@ -411,8 +478,8 @@
         if ipg_logdir:
           if not os.path.isdir(ipg_logdir):
             self.fail("Folder " + ipg_logdir + " doesn't exist")
-          logfile = ipg_utils.GenerateIPGLogFilename(log_dir=ipg_logdir,
-                                                     timestamp=True)
+          logfile = ipg_utils.GenerateIPGLogFilename(
+              log_dir=ipg_logdir, timestamp=True)
         ipg_utils.RunIPG(ipg_duration + ipg_delay, ipg_resolution, logfile)
         logfiles.append(logfile)
 
@@ -433,7 +500,7 @@
         print "Results saved in ", json_path
 
       summary = ipg_utils.ProcessResultsFromMultipleIPGRuns(
-        logfiles, ipg_delay, outliers, json_path)
+          logfiles, ipg_delay, outliers, json_path)
       logging.info("Summary: %s", str(summary))
 
 
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 00b9082..c121cc8 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -452,6 +452,7 @@
       "//base/test:test_support",
       "//components/crx_file",
       "//components/version_info:version_info",
+      "//content/test:test_support",
       "//device/usb/public/cpp:test_support",
       "//extensions:extensions_resources",
 
diff --git a/extensions/common/api/declarative_net_request.idl b/extensions/common/api/declarative_net_request.idl
index 04e68d0..5da6d46f 100644
--- a/extensions/common/api/declarative_net_request.idl
+++ b/extensions/common/api/declarative_net_request.idl
@@ -46,8 +46,8 @@
     block,
     // Redirect the network request.
     redirect,
-    // Allow the network request. The request won't be blocked or redirected
-    // even if there is a block or redirect rule which matches it.
+    // Allow the network request. The request won't be blocked even if there
+    // is a blocking rule which matches it.
     allow,
     // Remove request/response headers from the network request.
     removeHeaders
@@ -156,11 +156,34 @@
 
   interface Functions {
 
-    // TODO(crbug.com/930961): Enable documentation for these functions once
-    // they are implemented.
-    [nodoc] static void addDynamicRules(Rule[] rules, optional EmptyCallback callback);
-    [nodoc] static void removeDynamicRules(long[] rule_ids, optional EmptyCallback callback);
-    [nodoc] static void getDynamicRules(GetRulesCallback callback);
+    // Adds <code>rules</code> to the current set of dynamic rules for the
+    // extension. These rules are persisted across browser sessions.
+    // Note: <a href="#property-MAX_NUMBER_OF_DYNAMIC_RULES">
+    // MAX_NUMBER_OF_DYNAMIC_RULES</a> is the maximum number of dynamic rules an
+    // extension can add.
+    // |rules|: The rules to add.
+    // |callback|: Called once the given <code>rules</code> are added. In case
+    // of an error, $(ref:runtime.lastError) will be set to denote the error
+    // message and no rules will be added. This can happen for multiple reasons,
+    // such as invalid rule format, duplicate rule ID, rule count limit
+    // exceeded, internal errors, and others.
+    static void addDynamicRules(Rule[] rules, optional EmptyCallback callback);
+
+    // Removes rules corresponding to <code>rule_ids</code> from the current set
+    // of dynamic rules for the extension. Any <code>rule_ids</code> not already
+    // present are ignored. Note that static rules specified as part of the
+    // extension package can not be removed using this function.
+    // |rule_ids|: The IDs of dynamic rules to remove.
+    // |callback|: Called once the rules are removed. In case of an error,
+    // $(ref:runtime.lastError) will be set to denote the error message and no
+    // rules will be removed. This may happen due to internal errors.
+    static void removeDynamicRules(long[] rule_ids,
+        optional EmptyCallback callback);
+
+    // Returns the current set of dynamic rules for the extension.
+    // |callback|: Called with the set of dynamic rules. An error might be
+    // raised in case of transient internal errors.
+    static void getDynamicRules(GetRulesCallback callback);
 
     // Adds <code>page_patterns</code> to the set of allowed pages. Requests
     // from these pages are not intercepted by the extension. These are
@@ -173,11 +196,11 @@
     // <a href="/extensions/match_patterns">match patterns</a> which are to be
     // allowed.
     // |callback|: Called after the <code>page_patterns</code> have been added.
-    // chrome.runtime.lastError will be set in case of an error, for example if
+    // $(ref:runtime.lastError) will be set in case of an error, for example if
     // an invalid page pattern is specified or the extension exceeded the
     // maximum page patterns limit.
-
-    static void addAllowedPages(DOMString[] page_patterns, optional EmptyCallback callback);
+    static void addAllowedPages(DOMString[] page_patterns,
+        optional EmptyCallback callback);
 
     // Removes <code>page_patterns</code> from the set of allowed pages.
     // Note: Removing page patterns is atomic. In case of an error, no page
@@ -186,8 +209,9 @@
     // <a href="/extensions/match_patterns">match patterns</a> which are to be
     // removed.
     // |callback|: Called after the <code>page_patterns</code> have been
-    // removed. chrome.runtime.lastError will be set in case of an error.
-    static void removeAllowedPages(DOMString[] page_patterns, optional EmptyCallback callback);
+    // removed. $(ref:runtime.lastError) will be set in case of an error.
+    static void removeAllowedPages(DOMString[] page_patterns,
+        optional EmptyCallback callback);
 
     // Returns the current set of allowed pages.
     // |callback|: Called with the set of currently allowed pages.
diff --git a/extensions/common/component_extension_url_pattern_unittest.cc b/extensions/common/component_extension_url_pattern_unittest.cc
index c26eedc..b2e5b90 100644
--- a/extensions/common/component_extension_url_pattern_unittest.cc
+++ b/extensions/common/component_extension_url_pattern_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "content/public/test/test_utils.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
@@ -25,7 +26,7 @@
                       .Build();
   std::string error;
   EXPECT_FALSE(all_urls->permissions_data()->CanAccessPage(
-      GURL("chrome://settings"), kTabId, &error))
+      content::GetWebUIURL("settings"), kTabId, &error))
       << error;
   // Non-chrome scheme should be fine.
   EXPECT_TRUE(all_urls->permissions_data()->CanAccessPage(
@@ -43,7 +44,7 @@
                       .Build();
   std::string error;
   EXPECT_TRUE(all_urls->permissions_data()->CanAccessPage(
-      GURL("chrome://settings"), kTabId, &error))
+      content::GetWebUIURL("settings"), kTabId, &error))
       << error;
 }
 
@@ -51,12 +52,12 @@
   // Explicitly specifying a pattern that allows access to the chrome
   // scheme is OK.
   auto chrome_urls = ExtensionBuilder("chrome urls")
-                         .AddPermission("chrome://*/*")
+                         .AddPermission(content::GetWebUIURLString("*/*"))
                          .SetLocation(Manifest::COMPONENT)
                          .Build();
   std::string error;
   EXPECT_TRUE(chrome_urls->permissions_data()->CanAccessPage(
-      GURL("chrome://settings"), kTabId, &error))
+      content::GetWebUIURL("settings"), kTabId, &error))
       << error;
 }
 
diff --git a/extensions/common/features/simple_feature_unittest.cc b/extensions/common/features/simple_feature_unittest.cc
index eff9dbef..7bc3b00 100644
--- a/extensions/common/features/simple_feature_unittest.cc
+++ b/extensions/common/features/simple_feature_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_command_line.h"
 #include "base/values.h"
+#include "content/public/test/test_utils.h"
 #include "extensions/common/features/complex_feature.h"
 #include "extensions/common/features/feature_channel.h"
 #include "extensions/common/features/feature_session_type.h"
@@ -890,10 +891,10 @@
   // Create a webui feature available on trunk.
   SimpleFeature feature;
   feature.set_contexts({Feature::WEBUI_CONTEXT});
-  feature.set_matches({"chrome://settings/*"});
+  feature.set_matches({content::GetWebUIURLString("settings/*").c_str()});
   feature.set_channel(version_info::Channel::UNKNOWN);
 
-  const GURL kAllowlistedUrl("chrome://settings/foo");
+  const GURL kAllowlistedUrl(content::GetWebUIURL("settings/foo"));
   const GURL kOtherUrl("https://example.com");
 
   {
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index 3bedc432..993bd10 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -10,6 +10,8 @@
 
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/test_utils.h"
 #include "extensions/common/constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -63,7 +65,7 @@
 
 TEST(ExtensionURLPatternTest, Ports) {
   const struct {
-    const char* pattern;
+    const std::string pattern;
     URLPattern::ParseResult expected_result;
     const char* expected_port;
   } kTestPatterns[] = {
@@ -80,7 +82,8 @@
       {"http://foo:123456/", URLPattern::ParseResult::kInvalidPort, "*"},
       {"http://foo:80:80/monkey", URLPattern::ParseResult::kInvalidPort, "*"},
       {"file://foo:1234/bar", URLPattern::ParseResult::kSuccess, "*"},
-      {"chrome://foo:1234/bar", URLPattern::ParseResult::kInvalidPort, "*"},
+      {content::GetWebUIURLString("foo:1234/bar"),
+       URLPattern::ParseResult::kInvalidPort, "*"},
 
       // Port-like strings in the path should not trigger a warning.
       {"http://*/:1234", URLPattern::ParseResult::kSuccess, "*"},
@@ -283,16 +286,18 @@
 TEST(ExtensionURLPatternTest, Match9) {
   URLPattern pattern(kAllSchemes);
   EXPECT_EQ(URLPattern::ParseResult::kSuccess,
-            pattern.Parse("chrome://favicon/*"));
-  EXPECT_EQ("chrome", pattern.scheme());
+            pattern.Parse(content::GetWebUIURLString("favicon/*")));
+  EXPECT_EQ(content::kChromeUIScheme, pattern.scheme());
   EXPECT_EQ("favicon", pattern.host());
   EXPECT_FALSE(pattern.match_subdomains());
   EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
-  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
-  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/https://google.com")));
-  EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://history")));
+  EXPECT_TRUE(
+      pattern.MatchesURL(content::GetWebUIURL("favicon/http://google.com")));
+  EXPECT_TRUE(
+      pattern.MatchesURL(content::GetWebUIURL("favicon/https://google.com")));
+  EXPECT_FALSE(pattern.MatchesURL(content::GetWebUIURL("history")));
 }
 
 // *://
@@ -301,7 +306,7 @@
   EXPECT_EQ(URLPattern::ParseResult::kSuccess, pattern.Parse("*://*/*"));
   EXPECT_TRUE(pattern.MatchesScheme("http"));
   EXPECT_TRUE(pattern.MatchesScheme("https"));
-  EXPECT_FALSE(pattern.MatchesScheme("chrome"));
+  EXPECT_FALSE(pattern.MatchesScheme(content::kChromeUIScheme));
   EXPECT_FALSE(pattern.MatchesScheme("file"));
   EXPECT_FALSE(pattern.MatchesScheme("ftp"));
   EXPECT_TRUE(pattern.match_subdomains());
@@ -309,7 +314,8 @@
   EXPECT_FALSE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
-  EXPECT_FALSE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_FALSE(
+      pattern.MatchesURL(content::GetWebUIURL("favicon/http://google.com")));
   EXPECT_FALSE(pattern.MatchesURL(GURL("file:///foo/bar")));
   EXPECT_FALSE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
 }
@@ -318,7 +324,7 @@
 TEST(ExtensionURLPatternTest, Match11) {
   URLPattern pattern(kAllSchemes);
   EXPECT_EQ(URLPattern::ParseResult::kSuccess, pattern.Parse("<all_urls>"));
-  EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+  EXPECT_TRUE(pattern.MatchesScheme(content::kChromeUIScheme));
   EXPECT_TRUE(pattern.MatchesScheme("http"));
   EXPECT_TRUE(pattern.MatchesScheme("https"));
   EXPECT_TRUE(pattern.MatchesScheme("file"));
@@ -328,7 +334,8 @@
   EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_TRUE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
-  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_TRUE(
+      pattern.MatchesURL(content::GetWebUIURL("favicon/http://google.com")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
@@ -351,7 +358,7 @@
 TEST(ExtensionURLPatternTest, Match12) {
   URLPattern pattern(URLPattern::SCHEME_ALL);
   EXPECT_EQ(URLPattern::ParseResult::kSuccess, pattern.Parse("<all_urls>"));
-  EXPECT_TRUE(pattern.MatchesScheme("chrome"));
+  EXPECT_TRUE(pattern.MatchesScheme(content::kChromeUIScheme));
   EXPECT_TRUE(pattern.MatchesScheme("http"));
   EXPECT_TRUE(pattern.MatchesScheme("https"));
   EXPECT_TRUE(pattern.MatchesScheme("file"));
@@ -364,11 +371,12 @@
   EXPECT_TRUE(pattern.match_effective_tld());
   EXPECT_TRUE(pattern.match_all_urls());
   EXPECT_EQ("/*", pattern.path());
-  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
+  EXPECT_TRUE(
+      pattern.MatchesURL(content::GetWebUIURL("favicon/http://google.com")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo/bar")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("file://localhost/foo/bar")));
-  EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://newtab")));
+  EXPECT_TRUE(pattern.MatchesURL(content::GetWebUIURL("newtab")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("about:blank")));
   EXPECT_TRUE(pattern.MatchesURL(GURL("about:version")));
   EXPECT_TRUE(pattern.MatchesURL(
@@ -550,12 +558,12 @@
 }
 
 static const struct GetAsStringPatterns {
-  const char* pattern;
+  const std::string pattern;
 } kGetAsStringTestCases[] = {
     {"http://www/"},
     {"http://*/*"},
-    {"chrome://*/*"},
-    {"chrome://newtab/"},
+    {content::GetWebUIURLString("*/*")},
+    {content::GetWebUIURLString("newtab/")},
     {"about:*"},
     {"about:blank"},
     {"chrome-extension://*/*"},
@@ -662,7 +670,7 @@
   EXPECT_EQ("https://*/*", all_urls[1].GetAsString());
   EXPECT_EQ("file:///*", all_urls[2].GetAsString());
   EXPECT_EQ("ftp://*/*", all_urls[3].GetAsString());
-  EXPECT_EQ("chrome://*/*", all_urls[4].GetAsString());
+  EXPECT_EQ(content::GetWebUIURLString("*/*"), all_urls[4].GetAsString());
   EXPECT_EQ("chrome-extension://*/*", all_urls[5].GetAsString());
   EXPECT_EQ("filesystem://*/*", all_urls[6].GetAsString());
   EXPECT_EQ("ws://*/*", all_urls[7].GetAsString());
diff --git a/extensions/renderer/feature_cache_unittest.cc b/extensions/renderer/feature_cache_unittest.cc
index 4906461..b36e57ce 100644
--- a/extensions/renderer/feature_cache_unittest.cc
+++ b/extensions/renderer/feature_cache_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/stl_util.h"
 #include "components/crx_file/id_util.h"
+#include "content/public/test/test_utils.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -74,10 +75,10 @@
 
   // The chrome://extensions page is whitelisted for the management API.
   FakeContext webui_context = {Feature::WEBUI_CONTEXT, nullptr,
-                               GURL("chrome://extensions")};
+                               content::GetWebUIURL("extensions")};
   // chrome://baz is not whitelisted, and should not have access.
   FakeContext webui_context_without_access = {Feature::WEBUI_CONTEXT, nullptr,
-                                              GURL("chrome://baz")};
+                                              content::GetWebUIURL("baz")};
 
   EXPECT_TRUE(HasFeature(cache, webui_context, "management"));
   EXPECT_FALSE(HasFeature(cache, webui_context_without_access, "management"));
diff --git a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
index ad2b1e0..9f9f56c7 100644
--- a/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
+++ b/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java
@@ -312,8 +312,7 @@
             Log.d(TAG, "Not provisioned during openSession()");
 
             if (!sMediaCryptoDeferrer.isProvisioning()) {
-                startProvisioning();
-                return true;
+                return startProvisioning();
             }
 
             // Cannot provision. Defer MediaCrypto creation and try again later.
@@ -607,7 +606,10 @@
             nativeOnProvisioningComplete(mNativeMediaDrmBridge, true);
 
         } catch (android.media.NotProvisionedException e) {
-            startProvisioning();
+            if (!startProvisioning()) {
+                // Indicate that provisioning failed.
+                nativeOnProvisioningComplete(mNativeMediaDrmBridge, false);
+            }
         }
     }
 
@@ -1133,22 +1135,39 @@
         return mMediaDrm.getPropertyString(SECURITY_LEVEL);
     }
 
-    private void startProvisioning() {
+    /**
+     * Start provisioning. Returns true if a provisioning request can be
+     * generated and has been forwarded to C++ code for handling, false
+     * otherwise.
+     */
+    private boolean startProvisioning() {
         Log.d(TAG, "startProvisioning");
         assert !mProvisioningPending;
         mProvisioningPending = true;
         assert mMediaDrm != null;
 
         if (!isNativeMediaDrmBridgeValid()) {
-            return;
+            return false;
         }
 
         if (mRequiresMediaCrypto) {
             sMediaCryptoDeferrer.onProvisionStarted();
         }
 
-        MediaDrm.ProvisionRequest request = mMediaDrm.getProvisionRequest();
+        // getProvisionRequest() may fail with android.media.MediaDrm.MediaDrmStateException or
+        // android.media.MediaDrmResetException, both of which extend IllegalStateException. As
+        // these specific exceptions are only available in API 21 and 23 respectively, using the
+        // base exception so that this will work for all API versions.
+        MediaDrm.ProvisionRequest request;
+        try {
+            request = mMediaDrm.getProvisionRequest();
+        } catch (java.lang.IllegalStateException e) {
+            Log.e(TAG, "Failed to get provisioning request", e);
+            return false;
+        }
+
         nativeOnProvisionRequest(mNativeMediaDrmBridge, request.getDefaultUrl(), request.getData());
+        return true;
     }
 
     /**
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index a8b11a3..68e2dbd 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -231,10 +231,6 @@
 const base::Feature kUseAndroidOverlayAggressively{
     "UseAndroidOverlayAggressively", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Let video track be unselected when video is playing in the background.
-const base::Feature kBackgroundSrcVideoTrackOptimization{
-    "BackgroundSrcVideoTrackOptimization", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Let video without audio be paused when it is playing in the background.
 const base::Feature kBackgroundVideoPauseOptimization{
     "BackgroundVideoPauseOptimization", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index db13a78..aac465d 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -97,7 +97,6 @@
 MEDIA_EXPORT extern const base::Feature kAutoplayIgnoreWebAudio;
 MEDIA_EXPORT extern const base::Feature kAutoplayDisableSettings;
 MEDIA_EXPORT extern const base::Feature kAutoplayWhitelistSettings;
-MEDIA_EXPORT extern const base::Feature kBackgroundSrcVideoTrackOptimization;
 MEDIA_EXPORT extern const base::Feature kBackgroundVideoPauseOptimization;
 MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoder;
 MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoderIgnoreWorkarounds;
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 71d8e8a..e8e6f90 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -116,13 +116,6 @@
   return base::FeatureList::IsEnabled(kResumeBackgroundVideo);
 }
 
-bool IsBackgroundVideoTrackOptimizationEnabled(
-    WebMediaPlayer::LoadType load_type) {
-  // Background video track optimization is always enabled for MSE videos.
-  return load_type == WebMediaPlayer::LoadType::kLoadTypeMediaSource ||
-         base::FeatureList::IsEnabled(kBackgroundSrcVideoTrackOptimization);
-}
-
 bool IsBackgroundVideoPauseOptimizationEnabled() {
   return base::FeatureList::IsEnabled(kBackgroundVideoPauseOptimization);
 }
@@ -3222,8 +3215,7 @@
   // video. MSE video track switching on hide has gone through a field test.
   // TODO(tmathmeyer): Passing load_type_ won't be needed after src= field
   // testing is finished. see: http://crbug.com/709302
-  if (!is_background_video_track_optimization_supported_ ||
-      !IsBackgroundVideoTrackOptimizationEnabled(load_type_))
+  if (!is_background_video_track_optimization_supported_)
     return false;
 
   // Disable video track only for players with audio that match the criteria for
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index f40e4d6..940e7c9 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -1746,18 +1746,17 @@
 class WebMediaPlayerImplBackgroundBehaviorTest
     : public WebMediaPlayerImplTest,
       public ::testing::WithParamInterface<
-          std::tuple<bool, bool, int, int, bool, bool, bool, bool, bool>> {
+          std::tuple<bool, int, int, bool, bool, bool, bool, bool>> {
  public:
   // Indices of the tuple parameters.
   static const int kIsMediaSuspendEnabled = 0;
-  static const int kIsBackgroundOptimizationEnabled = 1;
-  static const int kDurationSec = 2;
-  static const int kAverageKeyframeDistanceSec = 3;
-  static const int kIsResumeBackgroundVideoEnabled = 4;
-  static const int kIsMediaSource = 5;
-  static const int kIsBackgroundPauseEnabled = 6;
-  static const int kIsPictureInPictureEnabled = 7;
-  static const int kIsBackgroundVideoPlaybackEnabled = 8;
+  static const int kDurationSec = 1;
+  static const int kAverageKeyframeDistanceSec = 2;
+  static const int kIsResumeBackgroundVideoEnabled = 3;
+  static const int kIsMediaSource = 4;
+  static const int kIsBackgroundPauseEnabled = 5;
+  static const int kIsPictureInPictureEnabled = 6;
+  static const int kIsBackgroundVideoPlaybackEnabled = 7;
 
   void SetUp() override {
     WebMediaPlayerImplTest::SetUp();
@@ -1766,11 +1765,6 @@
 
     std::string enabled_features;
     std::string disabled_features;
-    if (IsBackgroundOptimizationOn()) {
-      enabled_features += kBackgroundSrcVideoTrackOptimization.name;
-    } else {
-      disabled_features += kBackgroundSrcVideoTrackOptimization.name;
-    }
 
     if (IsBackgroundPauseOn()) {
       if (!enabled_features.empty())
@@ -1823,10 +1817,6 @@
     return std::get<kIsMediaSuspendEnabled>(GetParam());
   }
 
-  bool IsBackgroundOptimizationOn() {
-    return std::get<kIsBackgroundOptimizationEnabled>(GetParam());
-  }
-
   bool IsResumeBackgroundVideoEnabled() {
     return std::get<kIsResumeBackgroundVideoEnabled>(GetParam());
   }
@@ -1915,8 +1905,7 @@
        (GetAverageKeyframeDistanceSec() < GetMaxKeyframeDistanceSec()));
 
   EXPECT_EQ(matches_requirements, IsBackgroundOptimizationCandidate());
-  EXPECT_EQ(IsBackgroundOptimizationOn() && matches_requirements,
-            ShouldDisableVideoWhenHidden());
+  EXPECT_EQ(matches_requirements, ShouldDisableVideoWhenHidden());
 
   // Only pause audible videos if both media suspend and resume background
   // videos is on and background video playback is disabled. Background video
@@ -1926,8 +1915,8 @@
                 (IsMediaSuspendOn() && IsResumeBackgroundVideoEnabled()),
             ShouldPausePlaybackWhenHidden());
 
-  if (!IsBackgroundOptimizationOn() || !matches_requirements ||
-      !ShouldDisableVideoWhenHidden() || IsMediaSuspendOn()) {
+  if (!matches_requirements || !ShouldDisableVideoWhenHidden() ||
+      IsMediaSuspendOn()) {
     return;
   }
 
@@ -1952,7 +1941,6 @@
     WebMediaPlayerImplBackgroundBehaviorTest,
     ::testing::Combine(
         ::testing::Bool(),
-        ::testing::Bool(),
         ::testing::Values(
             WebMediaPlayerImpl::kMaxKeyframeDistanceToDisableBackgroundVideoMs /
                     base::Time::kMillisecondsPerSecond +
diff --git a/media/gpu/linux/platform_video_frame_utils.cc b/media/gpu/linux/platform_video_frame_utils.cc
index a4a4d4b..7d2a74c 100644
--- a/media/gpu/linux/platform_video_frame_utils.cc
+++ b/media/gpu/linux/platform_video_frame_utils.cc
@@ -47,7 +47,11 @@
   for (size_t i = 0; i < num_planes; ++i) {
     planes[i].stride = pixmap->GetDmaBufPitch(i);
     planes[i].offset = pixmap->GetDmaBufOffset(i);
-    planes[i].modifier = pixmap->GetDmaBufModifier(i);
+    // TODO(crbug.com/957381): Move the modifier variable to NativePixmapHandle
+    // from NativePixmapPlane.
+    // TODO(crbug.com/914700): Move the modifier vairable from
+    // VideoFrameLayout::Plane to VideoFrameLayout.
+    planes[i].modifier = pixmap->GetBufferFormatModifier();
     buffer_sizes[i] = planes[i].offset +
                       planes[i].stride * VideoFrame::Rows(i, pixel_format,
                                                           coded_size.height());
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index c3c4d03..2fd4954 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -13,7 +13,8 @@
 #include "base/atomic_sequence_num.h"
 #include "base/bind.h"
 #include "base/bit_cast.h"
-#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager.h"
@@ -279,14 +280,13 @@
     DCHECK(shared_bitmap_reporter_);
 
     // Allocate SharedMemory and notify display compositor of the allocation.
-    shared_memory_ = viz::bitmap_allocation::AllocateMappedBitmap(
-        resource_size(), viz::ResourceFormat::RGBA_8888);
-    mojo::ScopedSharedBufferHandle handle =
-        viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(
-            shared_memory_.get(), resource_size(),
-            viz::ResourceFormat::RGBA_8888);
-    shared_bitmap_reporter_->DidAllocateSharedBitmap(std::move(handle),
-                                                     shared_bitmap_id_);
+    base::MappedReadOnlyRegion shm =
+        viz::bitmap_allocation::AllocateSharedBitmap(
+            resource_size(), viz::ResourceFormat::RGBA_8888);
+    shared_mapping_ = std::move(shm.mapping);
+    shared_bitmap_reporter_->DidAllocateSharedBitmap(
+        viz::bitmap_allocation::ToMojoHandle(std::move(shm.region)),
+        shared_bitmap_id_);
   }
   ~SoftwarePlaneResource() override {
     shared_bitmap_reporter_->DidDeleteSharedBitmap(shared_bitmap_id_);
@@ -295,17 +295,17 @@
   const viz::SharedBitmapId& shared_bitmap_id() const {
     return shared_bitmap_id_;
   }
-  void* pixels() { return shared_memory_->memory(); }
+  void* pixels() { return shared_mapping_.memory(); }
 
   // Returns a memory dump GUID consistent across processes.
   base::UnguessableToken GetSharedMemoryGuid() const {
-    return shared_memory_->mapped_id();
+    return shared_mapping_.guid();
   }
 
  private:
   viz::SharedBitmapReporter* const shared_bitmap_reporter_;
   const viz::SharedBitmapId shared_bitmap_id_;
-  std::unique_ptr<base::SharedMemory> shared_memory_;
+  base::WritableSharedMemoryMapping shared_mapping_;
 
   DISALLOW_COPY_AND_ASSIGN(SoftwarePlaneResource);
 };
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 4d93a3f..29c14f1 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2606,6 +2606,9 @@
     "data/ssl/certificates/multi-root-crlset-unrelated.raw",
     "data/ssl/certificates/multi-root.keychain",
     "data/ssl/certificates/multivalue_rdn.pem",
+    "data/ssl/certificates/name-normalization-byteequal.pem",
+    "data/ssl/certificates/name-normalization-case-folding.pem",
+    "data/ssl/certificates/name-normalization-printable-utf8.pem",
     "data/ssl/certificates/name_constraint_bad.pem",
     "data/ssl/certificates/name_constraint_good.pem",
     "data/ssl/certificates/ndn.ca.crt",
diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc
index f7c8b29..b8ae1e99 100644
--- a/net/cert/cert_verify_proc.cc
+++ b/net/cert/cert_verify_proc.cc
@@ -29,6 +29,7 @@
 #include "net/cert/cert_verify_result.h"
 #include "net/cert/crl_set.h"
 #include "net/cert/internal/ocsp.h"
+#include "net/cert/internal/parse_certificate.h"
 #include "net/cert/internal/signature_algorithm.h"
 #include "net/cert/known_roots.h"
 #include "net/cert/ocsp_revocation_status.h"
@@ -36,6 +37,7 @@
 #include "net/cert/x509_certificate.h"
 #include "net/cert/x509_util.h"
 #include "net/der/encode_values.h"
+#include "third_party/boringssl/src/include/openssl/pool.h"
 #include "url/url_canon.h"
 
 #if defined(USE_NSS_CERTS)
@@ -609,6 +611,72 @@
   return rv;
 }
 
+// static
+void CertVerifyProc::LogNameNormalizationResult(
+    const std::string& histogram_suffix,
+    NameNormalizationResult result) {
+  base::UmaHistogramEnumeration(
+      std::string("Net.CertVerifier.NameNormalizationPrivateRoots") +
+          histogram_suffix,
+      result);
+}
+
+// static
+void CertVerifyProc::LogNameNormalizationMetrics(
+    const std::string& histogram_suffix,
+    X509Certificate* verified_cert,
+    bool is_issued_by_known_root) {
+  if (is_issued_by_known_root)
+    return;
+
+  if (verified_cert->intermediate_buffers().empty()) {
+    LogNameNormalizationResult(histogram_suffix,
+                               NameNormalizationResult::kChainLengthOne);
+    return;
+  }
+
+  std::vector<CRYPTO_BUFFER*> der_certs;
+  der_certs.push_back(verified_cert->cert_buffer());
+  for (const auto& buf : verified_cert->intermediate_buffers())
+    der_certs.push_back(buf.get());
+
+  ParseCertificateOptions options;
+  options.allow_invalid_serial_numbers = true;
+
+  std::vector<der::Input> subjects;
+  std::vector<der::Input> issuers;
+
+  for (auto* buf : der_certs) {
+    der::Input tbs_certificate_tlv;
+    der::Input signature_algorithm_tlv;
+    der::BitString signature_value;
+    ParsedTbsCertificate tbs;
+    if (!ParseCertificate(
+            der::Input(CRYPTO_BUFFER_data(buf), CRYPTO_BUFFER_len(buf)),
+            &tbs_certificate_tlv, &signature_algorithm_tlv, &signature_value,
+            nullptr /* errors*/) ||
+        !ParseTbsCertificate(tbs_certificate_tlv, options, &tbs,
+                             nullptr /*errors*/)) {
+      LogNameNormalizationResult(histogram_suffix,
+                                 NameNormalizationResult::kError);
+      return;
+    }
+    subjects.push_back(tbs.subject_tlv);
+    issuers.push_back(tbs.issuer_tlv);
+  }
+
+  for (size_t i = 0; i < subjects.size() - 1; ++i) {
+    if (issuers[i] != subjects[i + 1]) {
+      LogNameNormalizationResult(histogram_suffix,
+                                 NameNormalizationResult::kNormalized);
+      return;
+    }
+  }
+
+  LogNameNormalizationResult(histogram_suffix,
+                             NameNormalizationResult::kByteEqual);
+}
+
 // CheckNameConstraints verifies that every name in |dns_names| is in one of
 // the domains specified by |domains|.
 static bool CheckNameConstraints(const std::vector<std::string>& dns_names,
diff --git a/net/cert/cert_verify_proc.h b/net/cert/cert_verify_proc.h
index 5d3c4b3..8ab3bf5 100644
--- a/net/cert/cert_verify_proc.h
+++ b/net/cert/cert_verify_proc.h
@@ -54,6 +54,16 @@
     VERIFY_DISABLE_SYMANTEC_ENFORCEMENT = 1 << 3,
   };
 
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class NameNormalizationResult {
+    kError = 0,
+    kByteEqual = 1,
+    kNormalized = 2,
+    kChainLengthOne = 3,
+    kMaxValue = kChainLengthOne
+  };
+
   // Creates and returns the default CertVerifyProc. |cert_net_fetcher| may not
   // be used, depending on the implementation.
   static scoped_refptr<CertVerifyProc> CreateDefault(
@@ -101,6 +111,17 @@
   CertVerifyProc();
   virtual ~CertVerifyProc();
 
+  // Record a histogram of whether Name normalization was used in verifying the
+  // chain. This should only be called for successfully validated chains.
+  static void LogNameNormalizationResult(const std::string& histogram_suffix,
+                                         NameNormalizationResult result);
+
+  // Record a histogram of whether Name normalization was used in verifying the
+  // chain. This should only be called for successfully validated chains.
+  static void LogNameNormalizationMetrics(const std::string& histogram_suffix,
+                                          X509Certificate* verified_cert,
+                                          bool is_issued_by_known_root);
+
  private:
   friend class base::RefCountedThreadSafe<CertVerifyProc>;
   FRIEND_TEST_ALL_PREFIXES(CertVerifyProcTest, DigiNotarCerts);
diff --git a/net/cert/cert_verify_proc_android.cc b/net/cert/cert_verify_proc_android.cc
index 7b334b7..7bae0d2 100644
--- a/net/cert/cert_verify_proc_android.cc
+++ b/net/cert/cert_verify_proc_android.cc
@@ -369,6 +369,9 @@
   if (IsCertStatusError(verify_result->cert_status))
     return MapCertStatusToNetError(verify_result->cert_status);
 
+  LogNameNormalizationMetrics(".Android", verify_result->verified_cert.get(),
+                              verify_result->is_issued_by_known_root);
+
   return OK;
 }
 
diff --git a/net/cert/cert_verify_proc_builtin.cc b/net/cert/cert_verify_proc_builtin.cc
index 0687bb2..8f2fec34 100644
--- a/net/cert/cert_verify_proc_builtin.cc
+++ b/net/cert/cert_verify_proc_builtin.cc
@@ -661,9 +661,14 @@
   }
 
   // Write the results to |*verify_result|.
-  return AssignVerifyResult(input_cert, hostname, result, verification_type,
-                            checked_revocation_for_some_path,
-                            ssl_trust_store.get(), verify_result);
+  int error = AssignVerifyResult(
+      input_cert, hostname, result, verification_type,
+      checked_revocation_for_some_path, ssl_trust_store.get(), verify_result);
+  if (error == OK) {
+    LogNameNormalizationMetrics(".Builtin", verify_result->verified_cert.get(),
+                                verify_result->is_issued_by_known_root);
+  }
+  return error;
 }
 
 }  // namespace
diff --git a/net/cert/cert_verify_proc_ios.cc b/net/cert/cert_verify_proc_ios.cc
index 8aded2f14..88b6ca71 100644
--- a/net/cert/cert_verify_proc_ios.cc
+++ b/net/cert/cert_verify_proc_ios.cc
@@ -331,6 +331,9 @@
   if (IsCertStatusError(verify_result->cert_status))
     return MapCertStatusToNetError(verify_result->cert_status);
 
+  LogNameNormalizationMetrics(".IOS", verify_result->verified_cert.get(),
+                              verify_result->is_issued_by_known_root);
+
   return OK;
 }
 
diff --git a/net/cert/cert_verify_proc_mac.cc b/net/cert/cert_verify_proc_mac.cc
index 7d909138..548ce4f 100644
--- a/net/cert/cert_verify_proc_mac.cc
+++ b/net/cert/cert_verify_proc_mac.cc
@@ -1048,6 +1048,9 @@
     verify_result->cert_status |= CERT_STATUS_IS_EV;
   }
 
+  LogNameNormalizationMetrics(".Mac", verify_result->verified_cert.get(),
+                              verify_result->is_issued_by_known_root);
+
   return OK;
 }
 
diff --git a/net/cert/cert_verify_proc_nss.cc b/net/cert/cert_verify_proc_nss.cc
index 2e4bc71..7bb58da 100644
--- a/net/cert/cert_verify_proc_nss.cc
+++ b/net/cert/cert_verify_proc_nss.cc
@@ -996,6 +996,9 @@
     }
   }
 
+  LogNameNormalizationMetrics(".NSS", verify_result->verified_cert.get(),
+                              verify_result->is_issued_by_known_root);
+
   return OK;
 }
 
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index 67123cb8..463073da 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -2504,6 +2504,142 @@
   }
 }
 
+class CertVerifyProcNameNormalizationTest : public CertVerifyProcInternalTest {
+ protected:
+  void SetUp() override {
+    CertVerifyProcInternalTest::SetUp();
+
+    scoped_refptr<X509Certificate> root_cert =
+        ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
+    ASSERT_TRUE(root_cert);
+    test_root_.reset(new ScopedTestRoot(root_cert.get()));
+  }
+
+  std::string HistogramName() const {
+    std::string prefix("Net.CertVerifier.NameNormalizationPrivateRoots.");
+    switch (verify_proc_type()) {
+      case CERT_VERIFY_PROC_NSS:
+        return prefix + "NSS";
+      case CERT_VERIFY_PROC_ANDROID:
+        return prefix + "Android";
+      case CERT_VERIFY_PROC_IOS:
+        return prefix + "IOS";
+      case CERT_VERIFY_PROC_MAC:
+        return prefix + "Mac";
+      case CERT_VERIFY_PROC_WIN:
+        return prefix + "Win";
+      case CERT_VERIFY_PROC_BUILTIN:
+        return prefix + "Builtin";
+    }
+  }
+
+  void ExpectNormalizationHistogram(int verify_error) {
+    if (verify_error == OK) {
+      histograms_.ExpectUniqueSample(
+          HistogramName(), CertVerifyProc::NameNormalizationResult::kNormalized,
+          1);
+    } else {
+      histograms_.ExpectTotalCount(HistogramName(), 0);
+    }
+  }
+
+  void ExpectByteEqualHistogram() {
+    histograms_.ExpectUniqueSample(
+        HistogramName(), CertVerifyProc::NameNormalizationResult::kByteEqual,
+        1);
+  }
+
+ private:
+  std::unique_ptr<ScopedTestRoot> test_root_;
+  base::HistogramTester histograms_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         CertVerifyProcNameNormalizationTest,
+                         testing::ValuesIn(kAllCertVerifiers),
+                         VerifyProcTypeToName);
+
+// Tries to verify a chain where the leaf's issuer CN is PrintableString, while
+// the intermediate's subject CN is UTF8String, and verifies the proper
+// histogram is logged.
+TEST_P(CertVerifyProcNameNormalizationTest, StringType) {
+  scoped_refptr<X509Certificate> chain = CreateCertificateChainFromFile(
+      GetTestCertsDirectory(), "name-normalization-printable-utf8.pem",
+      X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+  ASSERT_TRUE(chain);
+
+  int flags = 0;
+  CertVerifyResult verify_result;
+  int error =
+      Verify(chain.get(), "example.test", flags, CRLSet::BuiltinCRLSet().get(),
+             CertificateList(), &verify_result);
+
+  switch (verify_proc_type()) {
+    case CERT_VERIFY_PROC_NSS:
+    case CERT_VERIFY_PROC_IOS:
+    case CERT_VERIFY_PROC_MAC:
+    case CERT_VERIFY_PROC_WIN:
+      EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
+      break;
+    case CERT_VERIFY_PROC_ANDROID:
+    case CERT_VERIFY_PROC_BUILTIN:
+      EXPECT_THAT(error, IsOk());
+      break;
+  }
+
+  ExpectNormalizationHistogram(error);
+}
+
+// Tries to verify a chain where the leaf's issuer CN and intermediate's
+// subject CN are both PrintableString but have differing case on the first
+// character, and verifies the proper histogram is logged.
+TEST_P(CertVerifyProcNameNormalizationTest, CaseFolding) {
+  scoped_refptr<X509Certificate> chain = CreateCertificateChainFromFile(
+      GetTestCertsDirectory(), "name-normalization-case-folding.pem",
+      X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+  ASSERT_TRUE(chain);
+
+  int flags = 0;
+  CertVerifyResult verify_result;
+  int error =
+      Verify(chain.get(), "example.test", flags, CRLSet::BuiltinCRLSet().get(),
+             CertificateList(), &verify_result);
+
+  switch (verify_proc_type()) {
+    case CERT_VERIFY_PROC_NSS:
+    case CERT_VERIFY_PROC_WIN:
+      EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
+      break;
+    case CERT_VERIFY_PROC_ANDROID:
+    case CERT_VERIFY_PROC_IOS:
+    case CERT_VERIFY_PROC_MAC:
+    case CERT_VERIFY_PROC_BUILTIN:
+      EXPECT_THAT(error, IsOk());
+      break;
+  }
+
+  ExpectNormalizationHistogram(error);
+}
+
+// Confirms that a chain generated by the generate-name-normalization-certs.py
+// script which does not require normalization validates ok, and that the
+// ByteEqual histogram is logged.
+TEST_P(CertVerifyProcNameNormalizationTest, ByteEqual) {
+  scoped_refptr<X509Certificate> chain = CreateCertificateChainFromFile(
+      GetTestCertsDirectory(), "name-normalization-byteequal.pem",
+      X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+  ASSERT_TRUE(chain);
+
+  int flags = 0;
+  CertVerifyResult verify_result;
+  int error =
+      Verify(chain.get(), "example.test", flags, CRLSet::BuiltinCRLSet().get(),
+             CertificateList(), &verify_result);
+
+  EXPECT_THAT(error, IsOk());
+  ExpectByteEqualHistogram();
+}
+
 // This is the same as CertVerifyProcInternalTest, but it additionally sets up
 // networking capabilities for the cert verifiers, and a test server that can be
 // used to serve mock responses for AIA/OCSP/CRL.
diff --git a/net/cert/cert_verify_proc_win.cc b/net/cert/cert_verify_proc_win.cc
index 95cfda85..9fdb3b4 100644
--- a/net/cert/cert_verify_proc_win.cc
+++ b/net/cert/cert_verify_proc_win.cc
@@ -1151,6 +1151,10 @@
       CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) {
     verify_result->cert_status |= CERT_STATUS_IS_EV;
   }
+
+  LogNameNormalizationMetrics(".Win", verify_result->verified_cert.get(),
+                              verify_result->is_issued_by_known_root);
+
   return OK;
 }
 
diff --git a/net/data/ssl/certificates/README b/net/data/ssl/certificates/README
index ee1d35bf..a33a5bf 100644
--- a/net/data/ssl/certificates/README
+++ b/net/data/ssl/certificates/README
@@ -352,3 +352,13 @@
 - key_usage_p256_both.pem
      Self-signed P-256 certificates with various combinations of keyUsage
      flags. Their private key is key_usage_p256.key.
+
+===== From net/data/ssl/scripts/generate-name-normalization-certs.py
+- name-normalization-printable-utf8.pem
+     Leaf's issuer CN is PrintableString, intermediate's subject CN is
+     UTF8String.
+- name-normalization-case-folding.pem
+     Leaf's issuer CN and intermediate's subject CN are both PrintableString
+     but have differing case on the first character.
+- name-normalization-byteequal.pem
+     Names are byte-equal.
diff --git a/net/data/ssl/certificates/name-normalization-byteequal.pem b/net/data/ssl/certificates/name-normalization-byteequal.pem
new file mode 100644
index 0000000..65dd12d1
--- /dev/null
+++ b/net/data/ssl/certificates/name-normalization-byteequal.pem
@@ -0,0 +1,151 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            0d:df:45:88:06:ab:77:aa:2d:95:0f:1f:46:dd:62:ac
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Intermediate for byte equality comparison
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Leaf for byte equality comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:12:d3:17:b3:9c:fb:b1:60:fb:1d:c9:c9:f0:
+                    dc:8f:ef:36:04:dd:a4:d8:c5:57:39:2c:e1:d6:16:
+                    48:37:13:f7:82:16:ca:db:ef:d1:c7:6e:a0:f3:bb:
+                    be:41:0e:24:b2:33:b1:b7:35:83:92:2b:09:31:4e:
+                    24:9b:2c:fd:e1:be:09:95:e1:3f:16:0f:b6:30:c1:
+                    0d:44:77:50:da:20:ff:aa:48:80:00:67:17:fe:aa:
+                    3e:4d:b6:02:e4:f5:11:b5:cc:31:2f:77:0f:44:b0:
+                    37:78:4e:ff:ec:62:64:0f:94:8a:a1:89:c3:76:9f:
+                    03:bd:d0:e2:2a:36:ec:fa:59:51:f5:57:7d:e1:95:
+                    a4:fb:a3:3c:87:9b:65:79:68:b7:91:38:fd:7a:b3:
+                    89:a9:96:85:22:f7:38:9c:60:52:be:1f:f7:8b:c1:
+                    68:d3:ea:96:1e:13:2a:04:4e:ba:33:ac:07:ea:d9:
+                    53:67:c7:b8:15:e9:1e:ca:92:4d:91:4f:d0:d8:11:
+                    34:9b:8b:f5:00:70:7b:a7:1a:43:a2:90:1a:54:5f:
+                    34:e1:79:2e:72:65:4f:66:49:fa:b9:71:6f:4b:a1:
+                    73:79:ee:80:42:18:6b:bb:a9:b9:ba:c4:16:a6:04:
+                    74:cc:60:68:6f:0e:6e:4b:01:25:9c:c3:cb:58:73:
+                    ed:f9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:example.test, IP Address:127.0.0.1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         5e:0a:28:c8:75:88:60:20:9d:64:29:7c:25:ee:08:b4:c6:44:
+         cd:df:15:b8:66:88:8e:20:7c:35:63:81:a6:5c:6f:4f:4a:d1:
+         b9:21:3c:d5:45:a4:59:bf:39:6b:f0:06:5b:46:3b:dc:c6:8f:
+         60:7e:f6:c1:ca:6a:c0:ef:3f:ac:d9:2b:32:c8:df:38:82:a1:
+         a7:6f:a6:eb:b5:0c:04:fe:bc:4c:56:40:84:23:e4:56:54:8b:
+         ed:63:5d:2e:e0:76:84:16:e8:aa:a8:2e:e6:45:a3:13:96:c4:
+         e2:e6:26:1c:e1:2d:40:92:6e:9c:35:62:fd:77:96:6c:9f:df:
+         05:30:95:6b:e4:6c:02:65:fe:44:b0:3f:bb:c0:99:f4:8f:79:
+         aa:2f:64:4d:d3:c0:84:1c:4b:c6:a8:8d:ac:d3:2a:17:45:56:
+         0d:31:40:3d:cb:2f:e8:74:68:18:92:10:57:08:ad:31:26:ea:
+         e4:bf:7f:a1:be:95:f1:84:46:27:8c:77:7a:0e:3a:dc:05:9a:
+         69:8c:72:6a:ee:ca:e2:d7:e0:73:fb:00:8a:10:62:5d:3e:45:
+         e5:59:eb:be:91:b1:a7:5e:27:7e:49:39:31:e1:5a:ee:e1:ba:
+         39:d4:8d:d7:b7:77:3a:31:f4:82:7d:a6:c5:dd:92:03:60:75:
+         1c:7e:91:9d
+-----BEGIN CERTIFICATE-----
+MIIDJDCCAgygAwIBAgIQDd9FiAard6otlQ8fRt1irDANBgkqhkiG9w0BAQsFADA0
+MTIwMAYDVQQDEylJbnRlcm1lZGlhdGUgZm9yIGJ5dGUgZXF1YWxpdHkgY29tcGFy
+aXNvbjAeFw0xMDAxMDEwNjAwMDBaFw0zMjEyMDEwNjAwMDBaMCwxKjAoBgNVBAMT
+IUxlYWYgZm9yIGJ5dGUgZXF1YWxpdHkgY29tcGFyaXNvbjCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAM0S0xeznPuxYPsdycnw3I/vNgTdpNjFVzks4dYW
+SDcT94IWytvv0cduoPO7vkEOJLIzsbc1g5IrCTFOJJss/eG+CZXhPxYPtjDBDUR3
+UNog/6pIgABnF/6qPk22AuT1EbXMMS93D0SwN3hO/+xiZA+UiqGJw3afA73Q4io2
+7PpZUfVXfeGVpPujPIebZXlot5E4/XqziamWhSL3OJxgUr4f94vBaNPqlh4TKgRO
+ujOsB+rZU2fHuBXpHsqSTZFP0NgRNJuL9QBwe6caQ6KQGlRfNOF5LnJlT2ZJ+rlx
+b0uhc3nugEIYa7upubrEFqYEdMxgaG8ObksBJZzDy1hz7fkCAwEAAaM6MDgwHQYD
+VR0RBBYwFIIMZXhhbXBsZS50ZXN0hwR/AAABMBcGA1UdIAQQMA4wDAYKKwYBBAHW
+eQIEATANBgkqhkiG9w0BAQsFAAOCAQEAXgooyHWIYCCdZCl8Je4ItMZEzd8VuGaI
+jiB8NWOBplxvT0rRuSE81UWkWb85a/AGW0Y73MaPYH72wcpqwO8/rNkrMsjfOIKh
+p2+m67UMBP68TFZAhCPkVlSL7WNdLuB2hBboqqgu5kWjE5bE4uYmHOEtQJJunDVi
+/XeWbJ/fBTCVa+RsAmX+RLA/u8CZ9I95qi9kTdPAhBxLxqiNrNMqF0VWDTFAPcsv
+6HRoGJIQVwitMSbq5L9/ob6V8YRGJ4x3eg463AWaaYxyau7K4tfgc/sAihBiXT5F
+5VnrvpGxp14nfkk5MeFa7uG6OdSN17d3OjH0gn2mxd2SA2B1HH6RnQ==
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            72:9a:5b:30:b2:f1:2a:96:49:3d:14:29:21:81:7c:76
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Testing CA
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Intermediate for byte equality comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:61:af:cc:65:9f:88:85:5a:83:ad:e8:fb:79:
+                    2d:c1:3d:0c:f3:88:b1:7b:ec:e9:14:9c:f0:b8:55:
+                    6d:27:b1:91:01:d0:81:fb:2a:84:2d:13:a2:ac:95:
+                    d8:30:8d:dd:66:78:38:43:ec:c5:80:65:13:95:9e:
+                    b6:b3:0d:d6:9b:28:45:d9:7e:10:d0:bb:bf:65:3d:
+                    68:6d:c8:82:89:35:02:2c:c9:6f:9e:03:0b:56:71:
+                    57:25:7d:3d:65:26:73:40:80:bb:97:27:ce:e0:d3:
+                    0f:42:09:d5:82:0e:1d:66:2f:35:8f:c7:89:c0:e9:
+                    36:6d:84:f8:9a:df:1b:eb:8d:84:3f:74:e6:f3:25:
+                    87:6a:c3:5d:5c:11:69:1f:cb:29:69:67:c0:6e:df:
+                    69:45:0c:16:bb:23:14:c1:45:99:fe:90:72:5d:5e:
+                    c9:0f:2d:b6:69:8a:fa:e7:2b:ba:0c:fb:f7:79:67:
+                    c7:e8:b4:9f:21:72:f9:38:18:27:c2:7a:b7:f9:47:
+                    1c:62:bd:8d:a4:a6:c6:57:96:6e:c1:38:5c:f4:1d:
+                    73:94:49:83:58:88:f3:0d:64:97:16:19:dc:d3:80:
+                    40:8c:d7:4f:25:c3:be:19:83:3a:92:62:0c:9c:f7:
+                    10:da:67:e1:5a:c8:ce:f6:9b:c7:e4:e5:e7:f8:13:
+                    c1:ed
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         83:05:f4:0a:38:98:70:fb:ca:39:a5:e6:e1:52:06:d9:8e:83:
+         54:34:10:86:d6:47:d5:0d:42:a8:5c:ec:f7:e4:ad:d3:30:7f:
+         96:38:c7:15:b7:0c:e6:05:9f:a1:b7:e6:2a:2b:c9:b2:b7:27:
+         67:85:5f:b1:2c:e8:71:88:00:92:9d:2b:6f:d2:0f:8a:f1:d7:
+         95:ac:f5:01:16:11:31:36:63:f6:4a:ee:50:96:3c:af:69:f0:
+         63:34:dc:6e:73:b2:e2:3c:64:d6:62:98:04:98:52:4a:30:67:
+         33:21:5a:c0:27:ff:3d:7b:69:a2:d2:c2:be:26:43:37:21:f6:
+         50:5f:e3:cf:a2:8e:4e:1d:dc:5b:e4:42:44:14:96:8f:c6:cd:
+         a6:59:9d:56:f9:49:09:eb:3c:46:12:5a:73:b2:f9:9c:7f:2d:
+         d4:05:0d:db:ec:0f:08:1f:83:af:44:01:e3:8b:4a:45:ff:f7:
+         ee:f1:b1:9f:f6:14:f6:0f:82:e3:fa:c9:e9:62:28:27:18:8e:
+         62:d7:ba:50:91:96:fc:2d:fa:b0:32:16:a2:eb:0f:bf:ea:a9:
+         66:d7:f1:9f:7e:3d:5a:ab:89:6e:40:be:d1:0f:a8:ed:60:60:
+         c0:82:4e:cb:34:f7:63:6c:b4:1e:fb:78:c9:13:f0:77:5d:25:
+         d9:ac:f8:97
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIQcppbMLLxKpZJPRQpIYF8djANBgkqhkiG9w0BAQsFADAV
+MRMwEQYDVQQDEwpUZXN0aW5nIENBMB4XDTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2
+MDAwMFowNDEyMDAGA1UEAxMpSW50ZXJtZWRpYXRlIGZvciBieXRlIGVxdWFsaXR5
+IGNvbXBhcmlzb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGYa/M
+ZZ+IhVqDrej7eS3BPQzziLF77OkUnPC4VW0nsZEB0IH7KoQtE6Ksldgwjd1meDhD
+7MWAZROVnrazDdabKEXZfhDQu79lPWhtyIKJNQIsyW+eAwtWcVclfT1lJnNAgLuX
+J87g0w9CCdWCDh1mLzWPx4nA6TZthPia3xvrjYQ/dObzJYdqw11cEWkfyylpZ8Bu
+32lFDBa7IxTBRZn+kHJdXskPLbZpivrnK7oM+/d5Z8fotJ8hcvk4GCfCerf5Rxxi
+vY2kpsZXlm7BOFz0HXOUSYNYiPMNZJcWGdzTgECM108lw74ZgzqSYgyc9xDaZ+Fa
+yM72m8fk5ef4E8HtAgMBAAGjLDAqMA8GA1UdEwEB/wQFMAMBAf8wFwYDVR0gBBAw
+DjAMBgorBgEEAdZ5AgQBMA0GCSqGSIb3DQEBCwUAA4IBAQCDBfQKOJhw+8o5pebh
+UgbZjoNUNBCG1kfVDUKoXOz35K3TMH+WOMcVtwzmBZ+ht+YqK8mytydnhV+xLOhx
+iACSnStv0g+K8deVrPUBFhExNmP2Su5QljyvafBjNNxuc7LiPGTWYpgEmFJKMGcz
+IVrAJ/89e2mi0sK+JkM3IfZQX+PPoo5OHdxb5EJEFJaPxs2mWZ1W+UkJ6zxGElpz
+svmcfy3UBQ3b7A8IH4OvRAHji0pF//fu8bGf9hT2D4Lj+snpYignGI5i17pQkZb8
+LfqwMhai6w+/6qlm1/Gffj1aq4luQL7RD6jtYGDAgk7LNPdjbLQe+3jJE/B3XSXZ
+rPiX
+-----END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/name-normalization-case-folding.pem b/net/data/ssl/certificates/name-normalization-case-folding.pem
new file mode 100644
index 0000000..68bff24c
--- /dev/null
+++ b/net/data/ssl/certificates/name-normalization-case-folding.pem
@@ -0,0 +1,151 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            ae:42:1e:0d:b1:00:6a:69:30:b1:86:8d:07:dc:40:79
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = intermediate for case folding comparison
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Leaf for case folding comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:12:d3:17:b3:9c:fb:b1:60:fb:1d:c9:c9:f0:
+                    dc:8f:ef:36:04:dd:a4:d8:c5:57:39:2c:e1:d6:16:
+                    48:37:13:f7:82:16:ca:db:ef:d1:c7:6e:a0:f3:bb:
+                    be:41:0e:24:b2:33:b1:b7:35:83:92:2b:09:31:4e:
+                    24:9b:2c:fd:e1:be:09:95:e1:3f:16:0f:b6:30:c1:
+                    0d:44:77:50:da:20:ff:aa:48:80:00:67:17:fe:aa:
+                    3e:4d:b6:02:e4:f5:11:b5:cc:31:2f:77:0f:44:b0:
+                    37:78:4e:ff:ec:62:64:0f:94:8a:a1:89:c3:76:9f:
+                    03:bd:d0:e2:2a:36:ec:fa:59:51:f5:57:7d:e1:95:
+                    a4:fb:a3:3c:87:9b:65:79:68:b7:91:38:fd:7a:b3:
+                    89:a9:96:85:22:f7:38:9c:60:52:be:1f:f7:8b:c1:
+                    68:d3:ea:96:1e:13:2a:04:4e:ba:33:ac:07:ea:d9:
+                    53:67:c7:b8:15:e9:1e:ca:92:4d:91:4f:d0:d8:11:
+                    34:9b:8b:f5:00:70:7b:a7:1a:43:a2:90:1a:54:5f:
+                    34:e1:79:2e:72:65:4f:66:49:fa:b9:71:6f:4b:a1:
+                    73:79:ee:80:42:18:6b:bb:a9:b9:ba:c4:16:a6:04:
+                    74:cc:60:68:6f:0e:6e:4b:01:25:9c:c3:cb:58:73:
+                    ed:f9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:example.test, IP Address:127.0.0.1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         55:e5:e4:83:89:65:9f:e3:1c:dd:7a:cc:df:36:8f:b8:13:09:
+         f2:12:2c:1a:cc:c6:7f:4b:a0:a8:c9:5d:d2:16:d6:97:db:c6:
+         99:4e:44:68:bc:7e:d0:6c:8d:56:6c:86:a3:cd:af:42:da:d3:
+         0b:e5:f9:22:9d:aa:c8:e1:71:39:a3:36:68:19:d1:b5:f1:7e:
+         bc:23:9b:fc:ea:ee:a7:8c:0d:3d:e8:ff:4b:9f:46:b9:d9:7a:
+         9a:77:40:d7:fa:63:76:52:61:64:f2:a2:8c:2a:61:83:d9:aa:
+         37:6c:db:a4:a2:2d:41:03:cc:5a:3c:09:6c:2e:10:5d:82:4d:
+         25:61:ad:12:21:4b:83:f2:6b:e1:4b:dd:3f:c1:e2:4f:32:f1:
+         72:cd:cc:49:cb:33:c5:bf:b4:78:07:13:83:b6:9f:eb:c8:11:
+         b7:90:17:54:90:1b:df:0d:a5:76:8c:f8:68:ec:b9:c1:6f:7a:
+         70:86:8a:10:e0:41:2c:c1:e1:c6:f8:9e:12:40:f4:87:2b:4e:
+         5a:88:b1:06:f8:52:6a:54:0b:78:b1:97:b4:bf:0e:bf:45:92:
+         1f:2f:b1:2e:87:11:e3:b7:02:fe:fa:cb:8e:a2:10:f1:10:5d:
+         05:56:03:ba:9b:8c:39:b3:d7:29:cf:58:c7:f0:ed:87:15:29:
+         d3:8f:9e:92
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAgugAwIBAgIRAK5CHg2xAGppMLGGjQfcQHkwDQYJKoZIhvcNAQELBQAw
+MzExMC8GA1UEAxMoaW50ZXJtZWRpYXRlIGZvciBjYXNlIGZvbGRpbmcgY29tcGFy
+aXNvbjAeFw0xMDAxMDEwNjAwMDBaFw0zMjEyMDEwNjAwMDBaMCsxKTAnBgNVBAMT
+IExlYWYgZm9yIGNhc2UgZm9sZGluZyBjb21wYXJpc29uMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAzRLTF7Oc+7Fg+x3JyfDcj+82BN2k2MVXOSzh1hZI
+NxP3ghbK2+/Rx26g87u+QQ4ksjOxtzWDkisJMU4kmyz94b4JleE/Fg+2MMENRHdQ
+2iD/qkiAAGcX/qo+TbYC5PURtcwxL3cPRLA3eE7/7GJkD5SKoYnDdp8DvdDiKjbs
++llR9Vd94ZWk+6M8h5tleWi3kTj9erOJqZaFIvc4nGBSvh/3i8Fo0+qWHhMqBE66
+M6wH6tlTZ8e4FekeypJNkU/Q2BE0m4v1AHB7pxpDopAaVF804XkucmVPZkn6uXFv
+S6Fzee6AQhhru6m5usQWpgR0zGBobw5uSwElnMPLWHPt+QIDAQABozowODAdBgNV
+HREEFjAUggxleGFtcGxlLnRlc3SHBH8AAAEwFwYDVR0gBBAwDjAMBgorBgEEAdZ5
+AgQBMA0GCSqGSIb3DQEBCwUAA4IBAQBV5eSDiWWf4xzdeszfNo+4EwnyEiwazMZ/
+S6CoyV3SFtaX28aZTkRovH7QbI1WbIajza9C2tML5fkinarI4XE5ozZoGdG18X68
+I5v86u6njA096P9Ln0a52Xqad0DX+mN2UmFk8qKMKmGD2ao3bNukoi1BA8xaPAls
+LhBdgk0lYa0SIUuD8mvhS90/weJPMvFyzcxJyzPFv7R4BxODtp/ryBG3kBdUkBvf
+DaV2jPho7LnBb3pwhooQ4EEsweHG+J4SQPSHK05aiLEG+FJqVAt4sZe0vw6/RZIf
+L7EuhxHjtwL++suOohDxEF0FVgO6m4w5s9cpz1jH8O2HFSnTj56S
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            09:cc:53:82:46:23:f7:28:e0:96:73:86:c4:50:79:9e
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Testing CA
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Intermediate for case folding comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:61:af:cc:65:9f:88:85:5a:83:ad:e8:fb:79:
+                    2d:c1:3d:0c:f3:88:b1:7b:ec:e9:14:9c:f0:b8:55:
+                    6d:27:b1:91:01:d0:81:fb:2a:84:2d:13:a2:ac:95:
+                    d8:30:8d:dd:66:78:38:43:ec:c5:80:65:13:95:9e:
+                    b6:b3:0d:d6:9b:28:45:d9:7e:10:d0:bb:bf:65:3d:
+                    68:6d:c8:82:89:35:02:2c:c9:6f:9e:03:0b:56:71:
+                    57:25:7d:3d:65:26:73:40:80:bb:97:27:ce:e0:d3:
+                    0f:42:09:d5:82:0e:1d:66:2f:35:8f:c7:89:c0:e9:
+                    36:6d:84:f8:9a:df:1b:eb:8d:84:3f:74:e6:f3:25:
+                    87:6a:c3:5d:5c:11:69:1f:cb:29:69:67:c0:6e:df:
+                    69:45:0c:16:bb:23:14:c1:45:99:fe:90:72:5d:5e:
+                    c9:0f:2d:b6:69:8a:fa:e7:2b:ba:0c:fb:f7:79:67:
+                    c7:e8:b4:9f:21:72:f9:38:18:27:c2:7a:b7:f9:47:
+                    1c:62:bd:8d:a4:a6:c6:57:96:6e:c1:38:5c:f4:1d:
+                    73:94:49:83:58:88:f3:0d:64:97:16:19:dc:d3:80:
+                    40:8c:d7:4f:25:c3:be:19:83:3a:92:62:0c:9c:f7:
+                    10:da:67:e1:5a:c8:ce:f6:9b:c7:e4:e5:e7:f8:13:
+                    c1:ed
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         b8:e0:27:8e:6f:76:22:82:5d:d6:aa:9c:f1:8d:f2:67:be:d5:
+         57:e9:79:a2:1f:a0:e7:20:e8:f8:c6:d9:8e:60:4d:57:22:9a:
+         bb:bc:10:d2:7e:6a:73:52:7c:b8:04:50:91:db:2d:31:db:da:
+         ba:0b:28:93:3d:2a:3d:78:1f:23:f6:df:46:a6:25:be:22:b2:
+         7d:fb:5c:97:fc:80:f1:86:69:0a:f8:29:90:5e:5f:61:bc:dc:
+         03:5c:6e:6e:46:f9:61:5c:51:33:dd:04:fa:bf:20:da:fc:b9:
+         0c:15:12:8d:c1:ae:57:d4:15:bc:b4:01:be:86:5e:03:e2:6e:
+         a9:39:7a:a9:37:44:f1:19:dd:5b:21:2c:ba:6b:d1:12:52:31:
+         a1:58:c8:0f:d5:8c:db:10:44:7d:73:be:97:5f:0e:6a:88:1c:
+         da:cc:71:d6:f0:68:34:e4:94:9a:cd:4c:a0:51:86:6d:ef:e4:
+         b4:29:39:2a:48:0c:5b:ad:c8:86:90:be:73:8d:3b:be:5e:a6:
+         17:7a:98:6b:0a:7b:89:00:e3:7f:f7:db:0a:29:13:23:7a:bc:
+         01:4d:f6:b0:59:60:1b:b7:91:d1:ec:e9:44:42:85:3d:8f:db:
+         64:d0:72:45:98:11:8f:2d:5b:81:ff:00:99:67:00:e3:3d:10:
+         f4:fa:d0:7e
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIQCcxTgkYj9yjglnOGxFB5njANBgkqhkiG9w0BAQsFADAV
+MRMwEQYDVQQDEwpUZXN0aW5nIENBMB4XDTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2
+MDAwMFowMzExMC8GA1UEAxMoSW50ZXJtZWRpYXRlIGZvciBjYXNlIGZvbGRpbmcg
+Y29tcGFyaXNvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZhr8xl
+n4iFWoOt6Pt5LcE9DPOIsXvs6RSc8LhVbSexkQHQgfsqhC0ToqyV2DCN3WZ4OEPs
+xYBlE5WetrMN1psoRdl+ENC7v2U9aG3Igok1AizJb54DC1ZxVyV9PWUmc0CAu5cn
+zuDTD0IJ1YIOHWYvNY/HicDpNm2E+JrfG+uNhD905vMlh2rDXVwRaR/LKWlnwG7f
+aUUMFrsjFMFFmf6Qcl1eyQ8ttmmK+ucrugz793lnx+i0nyFy+TgYJ8J6t/lHHGK9
+jaSmxleWbsE4XPQdc5RJg1iI8w1klxYZ3NOAQIzXTyXDvhmDOpJiDJz3ENpn4VrI
+zvabx+Tl5/gTwe0CAwEAAaMsMCowDwYDVR0TAQH/BAUwAwEB/zAXBgNVHSAEEDAO
+MAwGCisGAQQB1nkCBAEwDQYJKoZIhvcNAQELBQADggEBALjgJ45vdiKCXdaqnPGN
+8me+1VfpeaIfoOcg6PjG2Y5gTVcimru8ENJ+anNSfLgEUJHbLTHb2roLKJM9Kj14
+HyP230amJb4isn37XJf8gPGGaQr4KZBeX2G83ANcbm5G+WFcUTPdBPq/INr8uQwV
+Eo3BrlfUFby0Ab6GXgPibqk5eqk3RPEZ3VshLLpr0RJSMaFYyA/VjNsQRH1zvpdf
+DmqIHNrMcdbwaDTklJrNTKBRhm3v5LQpOSpIDFutyIaQvnONO75ephd6mGsKe4kA
+43/32wopEyN6vAFN9rBZYBu3kdHs6URChT2P22TQckWYEY8tW4H/AJlnAOM9EPT6
+0H4=
+-----END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/name-normalization-printable-utf8.pem b/net/data/ssl/certificates/name-normalization-printable-utf8.pem
new file mode 100644
index 0000000..9025915
--- /dev/null
+++ b/net/data/ssl/certificates/name-normalization-printable-utf8.pem
@@ -0,0 +1,152 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            5d:87:b7:b3:20:ca:71:41:44:3a:a4:68:71:bd:c6:ac
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Intermediate for PrintableString / Utf8String comparison
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Leaf for PrintableString / Utf8String comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:12:d3:17:b3:9c:fb:b1:60:fb:1d:c9:c9:f0:
+                    dc:8f:ef:36:04:dd:a4:d8:c5:57:39:2c:e1:d6:16:
+                    48:37:13:f7:82:16:ca:db:ef:d1:c7:6e:a0:f3:bb:
+                    be:41:0e:24:b2:33:b1:b7:35:83:92:2b:09:31:4e:
+                    24:9b:2c:fd:e1:be:09:95:e1:3f:16:0f:b6:30:c1:
+                    0d:44:77:50:da:20:ff:aa:48:80:00:67:17:fe:aa:
+                    3e:4d:b6:02:e4:f5:11:b5:cc:31:2f:77:0f:44:b0:
+                    37:78:4e:ff:ec:62:64:0f:94:8a:a1:89:c3:76:9f:
+                    03:bd:d0:e2:2a:36:ec:fa:59:51:f5:57:7d:e1:95:
+                    a4:fb:a3:3c:87:9b:65:79:68:b7:91:38:fd:7a:b3:
+                    89:a9:96:85:22:f7:38:9c:60:52:be:1f:f7:8b:c1:
+                    68:d3:ea:96:1e:13:2a:04:4e:ba:33:ac:07:ea:d9:
+                    53:67:c7:b8:15:e9:1e:ca:92:4d:91:4f:d0:d8:11:
+                    34:9b:8b:f5:00:70:7b:a7:1a:43:a2:90:1a:54:5f:
+                    34:e1:79:2e:72:65:4f:66:49:fa:b9:71:6f:4b:a1:
+                    73:79:ee:80:42:18:6b:bb:a9:b9:ba:c4:16:a6:04:
+                    74:cc:60:68:6f:0e:6e:4b:01:25:9c:c3:cb:58:73:
+                    ed:f9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:example.test, IP Address:127.0.0.1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         7c:35:51:8f:20:b1:2d:30:8a:0c:6b:ed:16:22:a1:ac:eb:4d:
+         6f:ff:fa:1e:1a:45:a5:84:92:fe:80:8d:fa:1f:b0:ca:5e:6e:
+         0a:d9:d7:a8:e4:2b:5c:31:40:34:31:dd:0c:bd:2d:74:f2:b5:
+         4f:ea:f7:5f:99:52:cc:47:b7:28:a2:f1:2f:55:66:42:05:6b:
+         48:f7:83:52:37:67:04:3b:32:e3:dc:18:5a:33:fe:12:0e:1d:
+         b4:81:62:3e:61:06:d1:57:90:9f:17:cf:50:5e:6d:97:5d:ec:
+         ad:9f:85:c0:19:72:94:64:0c:2a:28:a2:26:4a:81:2e:c4:48:
+         38:37:d2:1e:39:cf:b9:c0:f7:89:2f:42:22:5b:6d:66:a7:5b:
+         4a:bc:59:91:80:a8:bd:88:6e:4b:58:ec:ca:28:fa:2e:e3:54:
+         dc:df:f3:c4:d0:cd:48:8c:ed:62:4a:f3:30:52:8b:c3:55:de:
+         fd:f5:50:13:4e:10:ed:31:5a:c1:7d:3a:d9:c4:a8:f2:b5:19:
+         1f:8c:32:48:42:0e:2c:da:a1:16:23:84:49:a9:c3:87:fe:75:
+         a1:f3:61:49:70:60:66:29:10:cb:4c:48:c2:4d:1e:c0:8a:c4:
+         12:dd:ee:74:1c:b7:ff:9d:0d:04:36:d0:e4:0f:98:78:7e:e7:
+         e7:22:8b:32
+-----BEGIN CERTIFICATE-----
+MIIDQjCCAiqgAwIBAgIQXYe3syDKcUFEOqRocb3GrDANBgkqhkiG9w0BAQsFADBD
+MUEwPwYDVQQDEzhJbnRlcm1lZGlhdGUgZm9yIFByaW50YWJsZVN0cmluZyAvIFV0
+ZjhTdHJpbmcgY29tcGFyaXNvbjAeFw0xMDAxMDEwNjAwMDBaFw0zMjEyMDEwNjAw
+MDBaMDsxOTA3BgNVBAMTMExlYWYgZm9yIFByaW50YWJsZVN0cmluZyAvIFV0ZjhT
+dHJpbmcgY29tcGFyaXNvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AM0S0xeznPuxYPsdycnw3I/vNgTdpNjFVzks4dYWSDcT94IWytvv0cduoPO7vkEO
+JLIzsbc1g5IrCTFOJJss/eG+CZXhPxYPtjDBDUR3UNog/6pIgABnF/6qPk22AuT1
+EbXMMS93D0SwN3hO/+xiZA+UiqGJw3afA73Q4io27PpZUfVXfeGVpPujPIebZXlo
+t5E4/XqziamWhSL3OJxgUr4f94vBaNPqlh4TKgROujOsB+rZU2fHuBXpHsqSTZFP
+0NgRNJuL9QBwe6caQ6KQGlRfNOF5LnJlT2ZJ+rlxb0uhc3nugEIYa7upubrEFqYE
+dMxgaG8ObksBJZzDy1hz7fkCAwEAAaM6MDgwHQYDVR0RBBYwFIIMZXhhbXBsZS50
+ZXN0hwR/AAABMBcGA1UdIAQQMA4wDAYKKwYBBAHWeQIEATANBgkqhkiG9w0BAQsF
+AAOCAQEAfDVRjyCxLTCKDGvtFiKhrOtNb//6HhpFpYSS/oCN+h+wyl5uCtnXqOQr
+XDFANDHdDL0tdPK1T+r3X5lSzEe3KKLxL1VmQgVrSPeDUjdnBDsy49wYWjP+Eg4d
+tIFiPmEG0VeQnxfPUF5tl13srZ+FwBlylGQMKiiiJkqBLsRIODfSHjnPucD3iS9C
+IlttZqdbSrxZkYCovYhuS1jsyij6LuNU3N/zxNDNSIztYkrzMFKLw1Xe/fVQE04Q
+7TFawX062cSo8rUZH4wySEIOLNqhFiOESanDh/51ofNhSXBgZikQy0xIwk0ewIrE
+Et3udBy3/50NBDbQ5A+YeH7n5yKLMg==
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            47:dd:b9:fa:88:2a:f7:c2:8a:00:52:f2:07:7b:73:a4
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = Testing CA
+        Validity
+            Not Before: Jan  1 06:00:00 2010 GMT
+            Not After : Dec  1 06:00:00 2032 GMT
+        Subject: CN = Intermediate for PrintableString / Utf8String comparison
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:61:af:cc:65:9f:88:85:5a:83:ad:e8:fb:79:
+                    2d:c1:3d:0c:f3:88:b1:7b:ec:e9:14:9c:f0:b8:55:
+                    6d:27:b1:91:01:d0:81:fb:2a:84:2d:13:a2:ac:95:
+                    d8:30:8d:dd:66:78:38:43:ec:c5:80:65:13:95:9e:
+                    b6:b3:0d:d6:9b:28:45:d9:7e:10:d0:bb:bf:65:3d:
+                    68:6d:c8:82:89:35:02:2c:c9:6f:9e:03:0b:56:71:
+                    57:25:7d:3d:65:26:73:40:80:bb:97:27:ce:e0:d3:
+                    0f:42:09:d5:82:0e:1d:66:2f:35:8f:c7:89:c0:e9:
+                    36:6d:84:f8:9a:df:1b:eb:8d:84:3f:74:e6:f3:25:
+                    87:6a:c3:5d:5c:11:69:1f:cb:29:69:67:c0:6e:df:
+                    69:45:0c:16:bb:23:14:c1:45:99:fe:90:72:5d:5e:
+                    c9:0f:2d:b6:69:8a:fa:e7:2b:ba:0c:fb:f7:79:67:
+                    c7:e8:b4:9f:21:72:f9:38:18:27:c2:7a:b7:f9:47:
+                    1c:62:bd:8d:a4:a6:c6:57:96:6e:c1:38:5c:f4:1d:
+                    73:94:49:83:58:88:f3:0d:64:97:16:19:dc:d3:80:
+                    40:8c:d7:4f:25:c3:be:19:83:3a:92:62:0c:9c:f7:
+                    10:da:67:e1:5a:c8:ce:f6:9b:c7:e4:e5:e7:f8:13:
+                    c1:ed
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.4.1
+
+    Signature Algorithm: sha256WithRSAEncryption
+         7d:86:2f:d6:61:32:cf:65:91:bb:45:d6:db:53:bc:2c:56:26:
+         3f:7e:4b:fe:a9:b1:36:ae:68:18:7f:c9:8e:1e:c6:f6:c6:b8:
+         eb:17:6e:fc:86:74:39:a5:fb:f4:a8:66:ed:25:66:ed:ec:42:
+         8b:5a:80:8e:bf:cf:c4:ba:a8:f1:af:45:52:05:fc:2d:e7:4d:
+         0e:4c:5f:18:ff:78:09:69:a4:24:a0:66:48:1d:c7:d3:fa:29:
+         97:00:58:ae:9c:d8:02:4d:57:37:e1:32:e0:27:0a:04:8f:69:
+         aa:5d:96:30:4d:70:0e:e4:f0:e1:58:38:cc:ab:34:ab:c3:02:
+         80:75:69:d3:2c:78:5d:9c:79:6e:4c:6a:32:b1:33:59:99:54:
+         9e:7f:1f:d2:02:b6:8f:79:37:ea:d3:9e:51:d7:5e:cc:e6:56:
+         e5:d3:93:20:10:80:a9:41:79:00:d2:59:49:4b:95:b9:fa:ef:
+         85:9f:9d:49:81:07:49:31:e5:db:2b:5e:0d:1b:50:64:86:8e:
+         50:4b:0a:d0:cd:82:b0:82:bb:ee:0c:ff:18:e1:a7:24:b5:cf:
+         9e:0b:c4:2f:28:81:af:53:fb:66:ad:57:0f:7f:0f:07:40:5b:
+         75:46:fd:08:8e:0d:8d:e9:c4:6a:ca:ec:19:d2:98:fd:a7:10:
+         b1:24:81:43
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAfagAwIBAgIQR925+ogq98KKAFLyB3tzpDANBgkqhkiG9w0BAQsFADAV
+MRMwEQYDVQQDEwpUZXN0aW5nIENBMB4XDTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2
+MDAwMFowQzFBMD8GA1UEAww4SW50ZXJtZWRpYXRlIGZvciBQcmludGFibGVTdHJp
+bmcgLyBVdGY4U3RyaW5nIGNvbXBhcmlzb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDGYa/MZZ+IhVqDrej7eS3BPQzziLF77OkUnPC4VW0nsZEB0IH7
+KoQtE6Ksldgwjd1meDhD7MWAZROVnrazDdabKEXZfhDQu79lPWhtyIKJNQIsyW+e
+AwtWcVclfT1lJnNAgLuXJ87g0w9CCdWCDh1mLzWPx4nA6TZthPia3xvrjYQ/dObz
+JYdqw11cEWkfyylpZ8Bu32lFDBa7IxTBRZn+kHJdXskPLbZpivrnK7oM+/d5Z8fo
+tJ8hcvk4GCfCerf5RxxivY2kpsZXlm7BOFz0HXOUSYNYiPMNZJcWGdzTgECM108l
+w74ZgzqSYgyc9xDaZ+FayM72m8fk5ef4E8HtAgMBAAGjLDAqMA8GA1UdEwEB/wQF
+MAMBAf8wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgQBMA0GCSqGSIb3DQEBCwUAA4IB
+AQB9hi/WYTLPZZG7RdbbU7wsViY/fkv+qbE2rmgYf8mOHsb2xrjrF278hnQ5pfv0
+qGbtJWbt7EKLWoCOv8/Euqjxr0VSBfwt500OTF8Y/3gJaaQkoGZIHcfT+imXAFiu
+nNgCTVc34TLgJwoEj2mqXZYwTXAO5PDhWDjMqzSrwwKAdWnTLHhdnHluTGoysTNZ
+mVSefx/SAraPeTfq055R117M5lbl05MgEICpQXkA0llJS5W5+u+Fn51JgQdJMeXb
+K14NG1Bkho5QSwrQzYKwgrvuDP8Y4acktc+eC8QvKIGvU/tmrVcPfw8HQFt1Rv0I
+jg2N6cRqyuwZ0pj9pxCxJIFD
+-----END CERTIFICATE-----
diff --git a/net/data/ssl/scripts/generate-name-normalization-certs.py b/net/data/ssl/scripts/generate-name-normalization-certs.py
new file mode 100755
index 0000000..06429c1
--- /dev/null
+++ b/net/data/ssl/scripts/generate-name-normalization-certs.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python2.7
+# 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.
+
+'''Generates certificate chains for testing name normalization.'''
+
+import os
+import subprocess
+import sys
+
+sys.path.append(os.path.join('..', '..', '..', 'tools', 'testserver'))
+import minica
+
+
+def pretty_print_cert(der):
+  command = ["openssl", "x509", "-text", "-inform", "DER"]
+  p = subprocess.Popen(command,
+                       stdin=subprocess.PIPE,
+                       stdout=subprocess.PIPE)
+  result = p.communicate(der)
+  if p.returncode != 0:
+    raise RuntimeError("openssl failed: %s" % p.returncode)
+  return result[0]
+
+
+def writecerts(name, der_certs):
+  fn = os.path.join('..', 'certificates', name)
+
+  text_certs = []
+  print 'pretty printing', fn
+  for der in der_certs:
+    text_certs.append(pretty_print_cert(der))
+
+  print 'writing', fn
+  with open(fn, 'w') as f:
+    f.write('\n'.join(text_certs))
+
+
+def GenerateCertAndIntermediate(leaf_subject,
+                                leaf_issuer,
+                                intermediate_subject,
+                                ip_sans=None,
+                                dns_sans=None,
+                                serial=0):
+  if serial == 0:
+    serial = minica.RandomNumber(16)
+
+  intermediate_serial = minica.RandomNumber(16)
+
+  target_cert_der = minica.MakeCertificate(
+      leaf_issuer, leaf_subject, serial, minica.LEAF_KEY,
+      minica.INTERMEDIATE_KEY, ip_sans=ip_sans, dns_sans=dns_sans)
+
+  intermediate_cert_der = minica.MakeCertificate(
+      minica.ROOT_CN, intermediate_subject, intermediate_serial,
+      minica.INTERMEDIATE_KEY, minica.ROOT_KEY, is_ca=True)
+
+  return [target_cert_der, intermediate_cert_der]
+
+
+def GeneratePrintableStringUtf8StringChain():
+  namesuffix = " for PrintableString / Utf8String comparison"
+  issuer_name = "Intermediate" + namesuffix
+  certs = GenerateCertAndIntermediate(leaf_subject="Leaf" + namesuffix,
+                                      leaf_issuer=issuer_name,
+                                      intermediate_subject=unicode(issuer_name),
+                                      ip_sans=["\x7F\x00\x00\x01"],
+                                      dns_sans=["example.test"])
+  writecerts('name-normalization-printable-utf8.pem', certs)
+
+
+def GenerateCaseFoldChain():
+  namesuffix = " for case folding comparison"
+  issuer_name = "Intermediate" + namesuffix
+  certs = GenerateCertAndIntermediate(leaf_subject="Leaf" + namesuffix,
+                                      leaf_issuer=issuer_name.replace('I', 'i'),
+                                      intermediate_subject=issuer_name,
+                                      ip_sans=["\x7F\x00\x00\x01"],
+                                      dns_sans=["example.test"])
+  writecerts('name-normalization-case-folding.pem', certs)
+
+
+def GenerateNormalChain():
+  namesuffix = " for byte equality comparison"
+  issuer_name = "Intermediate" + namesuffix
+  certs = GenerateCertAndIntermediate(leaf_subject="Leaf" + namesuffix,
+                                      leaf_issuer=issuer_name,
+                                      intermediate_subject=issuer_name,
+                                      ip_sans=["\x7F\x00\x00\x01"],
+                                      dns_sans=["example.test"])
+  writecerts('name-normalization-byteequal.pem', certs)
+
+
+if __name__ == '__main__':
+  GeneratePrintableStringUtf8StringChain()
+  GenerateCaseFoldChain()
+  GenerateNormalChain()
diff --git a/net/tools/testserver/asn1.py b/net/tools/testserver/asn1.py
index c0e0398..167b08d3 100644
--- a/net/tools/testserver/asn1.py
+++ b/net/tools/testserver/asn1.py
@@ -15,6 +15,10 @@
   if type(obj) == types.StringType:
     # Strings are PRINTABLESTRING
     return TagAndLength(19, len(obj)) + obj
+  if type(obj) == types.UnicodeType:
+    # Encode Unicode strings as UTF8String.
+    utf8val = obj.encode('utf-8')
+    return TagAndLength(12, len(utf8val)) + utf8val
   if type(obj) == types.BooleanType:
     val = "\x00"
     if obj:
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 04937ef..f04a67e 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -10938,7 +10938,7 @@
 
     scoped_refptr<X509Certificate> root_cert =
         ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
-    CHECK_NE(static_cast<X509Certificate*>(nullptr), root_cert.get());
+    ASSERT_TRUE(root_cert);
     test_root_.reset(new ScopedTestRoot(root_cert.get()));
 
 #if defined(USE_NSS_CERTS)
diff --git a/services/network/cors/cors_url_loader_factory.h b/services/network/cors/cors_url_loader_factory.h
index 89616115..5fe64e8 100644
--- a/services/network/cors/cors_url_loader_factory.h
+++ b/services/network/cors/cors_url_loader_factory.h
@@ -54,6 +54,8 @@
   // URLLoaders.
   void ClearBindings();
 
+  size_t GetActiveLoaderCount() const { return loaders_.size(); }
+
  private:
   // Implements mojom::URLLoaderFactory.
   void CreateLoaderAndStart(mojom::URLLoaderRequest request,
diff --git a/services/network/cors/preflight_controller.cc b/services/network/cors/preflight_controller.cc
index c2f205d..3177ef7d 100644
--- a/services/network/cors/preflight_controller.cc
+++ b/services/network/cors/preflight_controller.cc
@@ -351,6 +351,10 @@
   (*emplaced_pair.first)->Request(loader_factory);
 }
 
+size_t PreflightController::ReportAndGatherCacheSizeMetric() {
+  return cache_.ReportAndGatherSizeMetric();
+}
+
 void PreflightController::RemoveLoader(PreflightLoader* loader) {
   auto it = loaders_.find(loader);
   DCHECK(it != loaders_.end());
diff --git a/services/network/cors/preflight_controller.h b/services/network/cors/preflight_controller.h
index c58251a..f73ba215 100644
--- a/services/network/cors/preflight_controller.h
+++ b/services/network/cors/preflight_controller.h
@@ -64,6 +64,9 @@
       const net::NetworkTrafficAnnotationTag& traffic_annotation,
       mojom::URLLoaderFactory* loader_factory);
 
+  // Reports and gather CORS preflight cache size metric.
+  size_t ReportAndGatherCacheSizeMetric();
+
  private:
   class PreflightLoader;
 
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index ba7bc6c7..0134fda 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -758,6 +758,17 @@
   return params_ && params_->skip_reporting_send_permission_check;
 }
 
+size_t NetworkContext::ReportAndGatherCorsPreflightCacheSizeMetric() {
+  return cors_preflight_controller_.ReportAndGatherCacheSizeMetric();
+}
+
+size_t NetworkContext::GatherActiveLoaderCount() {
+  size_t count = 0;
+  for (const auto& factory : url_loader_factories_)
+    count += factory->GetActiveLoaderCount();
+  return count;
+}
+
 void NetworkContext::ClearNetworkingHistorySince(
     base::Time time,
     base::OnceClosure completion_callback) {
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 10e7ac8..9e83d056 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -392,6 +392,12 @@
   // consulting NetworkContextClient.OnCanSendReportingReports()
   bool SkipReportingPermissionCheck() const;
 
+  // Reports and gather CORS preflight cache size metric.
+  size_t ReportAndGatherCorsPreflightCacheSizeMetric();
+
+  // Gather active URLLoader count.
+  size_t GatherActiveLoaderCount();
+
  private:
   class ContextNetworkDelegate;
 
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index f42572a..cc8267d 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -232,6 +232,9 @@
   DCHECK(!g_network_service);
   g_network_service = this;
 
+  metrics_trigger_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(5), this,
+                               &NetworkService::ReportMetrics);
+
   // In testing environments, |service_request| may not be provided.
   if (service_request.is_pending())
     service_binding_.Bind(std::move(service_request));
@@ -815,6 +818,17 @@
   MaybeStartUpdateLoadInfoTimer();
 }
 
+void NetworkService::ReportMetrics() {
+  size_t cache_size = 0;
+  size_t loader_count = 0;
+  for (auto* context : network_contexts_) {
+    cache_size += context->ReportAndGatherCorsPreflightCacheSizeMetric();
+    loader_count += context->GatherActiveLoaderCount();
+  }
+  UMA_HISTOGRAM_COUNTS_10000("Net.Cors.PreflightCacheTotalEntries", cache_size);
+  UMA_HISTOGRAM_COUNTS_1000("Net.Cors.ActiveLoaderCount", loader_count);
+}
+
 void NetworkService::Bind(mojom::NetworkServiceRequest request) {
   DCHECK(!binding_.is_bound());
   binding_.Bind(std::move(request));
diff --git a/services/network/network_service.h b/services/network/network_service.h
index 86d0ee1..1db705e 100644
--- a/services/network/network_service.h
+++ b/services/network/network_service.h
@@ -275,6 +275,9 @@
   // Starts timer call UpdateLoadInfo() again, if needed.
   void AckUpdateLoadInfo();
 
+  // Reports metrics on a periodically triggered repeating timer.
+  void ReportMetrics();
+
   service_manager::ServiceBinding service_binding_{this};
 
   bool initialized_ = false;
@@ -354,6 +357,9 @@
   // acknowledged.
   bool waiting_on_load_state_ack_ = false;
 
+  // A timer that periodically calls ReportMetrics every hour.
+  base::RepeatingTimer metrics_trigger_timer_;
+
   DISALLOW_COPY_AND_ASSIGN(NetworkService);
 };
 
diff --git a/services/network/public/cpp/cors/preflight_cache.cc b/services/network/public/cpp/cors/preflight_cache.cc
index 37065c97..40403d5 100644
--- a/services/network/public/cpp/cors/preflight_cache.cc
+++ b/services/network/public/cpp/cors/preflight_cache.cc
@@ -6,7 +6,6 @@
 
 #include <iterator>
 
-#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
 #include "base/time/time.h"
@@ -36,11 +35,7 @@
 
 }  // namespace
 
-PreflightCache::PreflightCache() {
-  timer_.Start(FROM_HERE, base::TimeDelta::FromHours(1), this,
-               &PreflightCache::ReportMetrics);
-}
-
+PreflightCache::PreflightCache() = default;
 PreflightCache::~PreflightCache() = default;
 
 void PreflightCache::AppendEntry(
@@ -100,6 +95,12 @@
   return false;
 }
 
+size_t PreflightCache::ReportAndGatherSizeMetric() {
+  size_t entries = CountEntries();
+  UMA_HISTOGRAM_COUNTS_10000("Net.Cors.PreflightCacheEntries", entries);
+  return entries;
+}
+
 size_t PreflightCache::CountOriginsForTesting() const {
   return cache_.size();
 }
@@ -134,11 +135,6 @@
   }
 }
 
-void PreflightCache::ReportMetrics() {
-  base::UmaHistogramCounts10000("Net.Cors.PreflightCacheEntries",
-                                CountEntries());
-}
-
 }  // namespace cors
 
 }  // namespace network
diff --git a/services/network/public/cpp/cors/preflight_cache.h b/services/network/public/cpp/cors/preflight_cache.h
index b77be4f9..5fbd3f6 100644
--- a/services/network/public/cpp/cors/preflight_cache.h
+++ b/services/network/public/cpp/cors/preflight_cache.h
@@ -11,7 +11,6 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
-#include "base/timer/timer.h"
 #include "net/http/http_request_headers.h"
 #include "services/network/public/cpp/cors/preflight_result.h"
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
@@ -48,6 +47,9 @@
       const net::HttpRequestHeaders& headers,
       bool is_revalidating);
 
+  // Reports and gather CORS preflight cache size metric.
+  size_t ReportAndGatherSizeMetric();
+
   // Counts cached origins for testing.
   size_t CountOriginsForTesting() const;
 
@@ -61,7 +63,6 @@
  private:
   size_t CountEntries() const;
   void MayPurge(size_t max_entries);
-  void ReportMetrics();
 
   // A map for caching. The outer map takes an origin to find a per-origin
   // cache map, and the inner map takes an URL to find a cached entry.
@@ -69,9 +70,6 @@
            std::map<std::string /* url */, std::unique_ptr<PreflightResult>>>
       cache_;
 
-  // RepeatingTimer to report metrics.
-  base::RepeatingTimer timer_;
-
   DISALLOW_COPY_AND_ASSIGN(PreflightCache);
 };
 
diff --git a/services/resource_coordinator/public/mojom/BUILD.gn b/services/resource_coordinator/public/mojom/BUILD.gn
index 5f79114..1159b61 100644
--- a/services/resource_coordinator/public/mojom/BUILD.gn
+++ b/services/resource_coordinator/public/mojom/BUILD.gn
@@ -14,7 +14,6 @@
     "memory_instrumentation/constants.mojom",
     "memory_instrumentation/memory_instrumentation.mojom",
     "service_constants.mojom",
-    "webui_graph_dump.mojom",
   ]
 
   public_deps = [
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index a84a225..ef043e4e 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -821,7 +821,10 @@
     sources += [ "ext/test_fonts_android.cc" ]
   }
   if (is_fuchsia) {
-    sources += [ "ext/test_fonts_fuchsia.cc" ]
+    sources += [
+      "ext/test_fonts_fuchsia.cc",
+      "ext/test_fonts_fuchsia.h",
+    ]
   }
   if (is_linux) {
     sources += [ "ext/test_fonts_linux.cc" ]
@@ -854,6 +857,7 @@
 
   deps = [
     ":skia",
+    ":test_fonts",
     "//base",
     "//base/test:test_support",
     "//mojo/core/test:run_all_unittests",
diff --git a/skia/ext/fontmgr_fuchsia_unittest.cc b/skia/ext/fontmgr_fuchsia_unittest.cc
index d3fc113e..7ac1aaa0 100644
--- a/skia/ext/fontmgr_fuchsia_unittest.cc
+++ b/skia/ext/fontmgr_fuchsia_unittest.cc
@@ -5,8 +5,7 @@
 #include <fuchsia/fonts/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
 
-#include "base/fuchsia/service_directory_client.h"
-#include "base/path_service.h"
+#include "skia/ext/test_fonts_fuchsia.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkFontMgr.h"
 #include "third_party/skia/include/core/SkTypeface.h"
@@ -19,19 +18,17 @@
  public:
   FuchsiaFontManagerTest() {
     font_manager_ = SkFontMgr_New_Fuchsia(
-        base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
-            ->ConnectToServiceSync<fuchsia::fonts::Provider>());
+        RunTestProviderWithTestFonts(&font_provider_controller_));
   }
 
  protected:
+  fidl::InterfaceHandle<fuchsia::sys::ComponentController>
+      font_provider_controller_;
   sk_sp<SkFontMgr> font_manager_;
 };
 
 // Verify that SkTypeface objects are cached.
-// TODO(https://crbug.com/931333): Currently font provider returns the same
-// font for sans and serif when used with the default font config. Update this
-// test to use the fonts //third_party/test_fonts.
-TEST_F(FuchsiaFontManagerTest, DISABLED_Caching) {
+TEST_F(FuchsiaFontManagerTest, Caching) {
   sk_sp<SkTypeface> sans(
       font_manager_->matchFamilyStyle("sans", SkFontStyle()));
   EXPECT_TRUE(sans);
diff --git a/skia/ext/test_fonts_fuchsia.cc b/skia/ext/test_fonts_fuchsia.cc
index d277605..fa3667dc 100644
--- a/skia/ext/test_fonts_fuchsia.cc
+++ b/skia/ext/test_fonts_fuchsia.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "skia/ext/test_fonts.h"
+#include "skia/ext/test_fonts_fuchsia.h"
 
 #include <fuchsia/fonts/cpp/fidl.h>
 #include <fuchsia/io/cpp/fidl.h>
@@ -14,20 +14,14 @@
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "skia/ext/fontmgr_default.h"
+#include "skia/ext/test_fonts.h"
 #include "third_party/skia/include/core/SkFontMgr.h"
 #include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
 
 namespace skia {
 
-void ConfigureTestFont() {
-  // ComponentController for the font provider service started below. It's a
-  // static field to keep the service running until the test process is
-  // destroyed.
-  static base::NoDestructor<
-      fidl::InterfaceHandle<fuchsia::sys::ComponentController>>
-      test_font_provider_controller;
-  DCHECK(!*test_font_provider_controller);
-
+fuchsia::fonts::ProviderSyncPtr RunTestProviderWithTestFonts(
+    fidl::InterfaceHandle<fuchsia::sys::ComponentController>* controller_out) {
   // Start a fuchsia.fonts.Provider instance and configure it to load the test
   // fonts, which must be bundled in the calling process' package.
   fuchsia::sys::LaunchInfo launch_info;
@@ -53,14 +47,26 @@
       base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToServiceSync<fuchsia::sys::Launcher>();
   launcher->CreateComponent(std::move(launch_info),
-                            test_font_provider_controller->NewRequest());
+                            controller_out->NewRequest());
 
   base::fuchsia::ServiceDirectoryClient font_provider_services_client(
       std::move(font_provider_services_dir));
 
+  return font_provider_services_client
+      .ConnectToServiceSync<fuchsia::fonts::Provider>();
+}
+
+void ConfigureTestFont() {
+  // ComponentController for the font provider service started below. It's a
+  // static field to keep the service running until the test process is
+  // destroyed.
+  static base::NoDestructor<
+      fidl::InterfaceHandle<fuchsia::sys::ComponentController>>
+      test_font_provider_controller;
+  DCHECK(!*test_font_provider_controller);
+
   skia::OverrideDefaultSkFontMgr(SkFontMgr_New_Fuchsia(
-      font_provider_services_client
-          .ConnectToServiceSync<fuchsia::fonts::Provider>()));
+      RunTestProviderWithTestFonts(test_font_provider_controller.get())));
 }
 
 }  // namespace skia
diff --git a/skia/ext/test_fonts_fuchsia.h b/skia/ext/test_fonts_fuchsia.h
new file mode 100644
index 0000000..f7aefac
--- /dev/null
+++ b/skia/ext/test_fonts_fuchsia.h
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SKIA_EXT_TEST_FONTS_FUCHSIA_H_
+#define SKIA_EXT_TEST_FONTS_FUCHSIA_H_
+
+#include <fuchsia/fonts/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+
+namespace skia {
+
+fuchsia::fonts::ProviderSyncPtr RunTestProviderWithTestFonts(
+    fidl::InterfaceHandle<fuchsia::sys::ComponentController>* controller_out);
+
+}  // namespace skia
+
+#endif  // SKIA_EXT_TEST_FONTS_FUCHSIA_H_
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 8c0f7b2..d654a87 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -567,7 +567,7 @@
           ],
           "idempotent": false
         },
-        "test": "chrome_kevin_tast_tests",
+        "test": "chrome_all_tast_tests",
         "trigger_script": {
           "script": "//testing/trigger_scripts/chromeos_device_trigger.py"
         }
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 88162d9..fe89411 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -568,13 +568,6 @@
     "label": "//chrome/android:chrome_junit_tests",
     "type": "junit_test",
   },
-  "chrome_kevin_tast_tests": {
-    "args": [
-      "--vm-logs-dir=${ISOLATED_OUTDIR}",
-    ],
-    "label": "//chromeos:chrome_kevin_tast_tests",
-    "type": "raw",
-  },
   "chrome_login_tast_tests": {
     "args": [
       "--vm-logs-dir=${ISOLATED_OUTDIR}",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index e77cf7d..6081fd43 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -316,7 +316,7 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/chromeos.base_unittests.filter',
         ],
       },
-      'chrome_kevin_tast_tests': {
+      'chrome_all_tast_tests': {
         'swarming': {
           'idempotent': False,  # https://crbug.com/923426#c27
         },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 83bdc4f5b..eda6dfb 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1136,31 +1136,6 @@
             ]
         }
     ],
-    "BackgroundVideoOptimizations": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows",
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "BackgroundSrcVideoTrackOptimization"
-                    ]
-                },
-                {
-                    "name": "Control",
-                    "disable_features": [
-                        "BackgroundSrcVideoTrackOptimization"
-                    ]
-                }
-            ]
-        }
-    ],
     "BlinkGenPropertyTrees": [
         {
             "platforms": [
diff --git a/third_party/android_crazy_linker/BUILD.gn b/third_party/android_crazy_linker/BUILD.gn
index a7c21e02..5747a16 100644
--- a/third_party/android_crazy_linker/BUILD.gn
+++ b/third_party/android_crazy_linker/BUILD.gn
@@ -85,6 +85,7 @@
         "src/src/crazy_linker_library_view.h",
         "src/src/crazy_linker_line_reader.cpp",
         "src/src/crazy_linker_line_reader.h",
+        "src/src/crazy_linker_load_params.h",
         "src/src/crazy_linker_memory_mapping.cpp",
         "src/src/crazy_linker_memory_mapping.h",
         "src/src/crazy_linker_pointer_set.cpp",
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_api.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_api.cpp
index eeda97c..e7b7413 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_api.cpp
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_api.cpp
@@ -109,9 +109,22 @@
                                   const char* lib_name,
                                   crazy_context_t* context) {
   ScopedLockedGlobals globals;
-  LibraryView* view = globals->libraries()->LoadLibrary(
-      lib_name, context->load_address, globals->search_path_list(),
-      &context->error);
+  crazy::LibraryList* libs = globals->libraries();
+  crazy::LoadParams params;
+  params.wanted_address = context->load_address;
+  crazy::Expected<LibraryView*> found =
+      libs->FindAndCheckLoadedLibrary(lib_name, params, &context->error);
+  if (!found.has_value())
+    return CRAZY_STATUS_FAILURE;
+
+  LibraryView* view = found.value();
+  if (!view) {
+    if (!libs->LocateLibraryFile(lib_name, *globals->search_path_list(),
+                                 &params, &context->error)) {
+      return CRAZY_STATUS_FAILURE;
+    }
+    view = libs->LoadLibraryInternal(params, &context->error);
+  }
 
   if (!view)
     return CRAZY_STATUS_FAILURE;
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.cpp
index d219b3c..80e3c92 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.cpp
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.cpp
@@ -27,10 +27,7 @@
  public:
   ~InternalElfLoader();
 
-  bool LoadAt(const char* lib_path,
-              off_t file_offset,
-              uintptr_t wanted_address,
-              Error* error);
+  bool LoadAt(const LoadParams& params, Error* error);
 
   // Only call the following functions after a successful LoadAt() call.
 
@@ -56,7 +53,6 @@
   ELF::Addr phdr_size_ = 0;  // and its size.
 
   off_t file_offset_ = 0;
-  void* wanted_load_address_ = nullptr;
   void* load_start_ = nullptr;  // First page of reserved address space.
   ELF::Addr load_size_ = 0;     // Size in bytes of reserved address space.
   ELF::Addr load_bias_ = 0;     // load_bias, add this value to all "vaddr"
@@ -71,7 +67,7 @@
   // Individual steps used by ::LoadAt()
   bool ReadElfHeader(Error* error);
   bool ReadProgramHeader(Error* error);
-  bool ReserveAddressSpace(Error* error);
+  bool ReserveAddressSpace(const LoadParams& params, Error* error);
   bool LoadSegments(Error* error);
   bool FindPhdr(Error* error);
   bool CheckPhdr(ELF::Addr, Error* error);
@@ -84,25 +80,25 @@
   }
 }
 
-bool InternalElfLoader::LoadAt(const char* lib_path,
-                               off_t file_offset,
-                               uintptr_t wanted_address,
-                               Error* error) {
-  LOG("lib_path='%s', file_offset=%p, load_address=%p", lib_path, file_offset,
-      wanted_address);
+bool InternalElfLoader::LoadAt(const LoadParams& params, Error* error) {
+  const char* lib_path = params.library_path.c_str();
+  LOG("lib_path='%s', file_offset=%p, load_address=%lx", lib_path,
+      params.library_offset, static_cast<unsigned long>(params.wanted_address));
 
   // Check that the load address is properly page-aligned.
+  uintptr_t wanted_address = params.wanted_address;
   if (wanted_address != PAGE_START(wanted_address)) {
     error->Format("Load address is not page aligned (%08x)", wanted_address);
     return false;
   }
-  wanted_load_address_ = reinterpret_cast<void*>(wanted_address);
 
   // Check that the file offset is also properly page-aligned.
   // PAGE_START() can't be used here due to the compiler complaining about
   // comparing signed (off_t) and unsigned (size_t) values.
+  off_t file_offset = params.library_offset;
   if ((file_offset & static_cast<off_t>(PAGE_SIZE - 1)) != 0) {
-    error->Format("File offset is not page aligned (%08x)", file_offset);
+    error->Format("File offset is not page aligned (%08lx)",
+                  static_cast<unsigned long>(file_offset));
     return false;
   }
   file_offset_ = file_offset;
@@ -122,7 +118,7 @@
   path_ = lib_path;
 
   if (!ReadElfHeader(error) || !ReadProgramHeader(error) ||
-      !ReserveAddressSpace(error)) {
+      !ReserveAddressSpace(params, error)) {
     return false;
   }
 
@@ -220,7 +216,8 @@
 // This will use the wanted_load_address_ value. Fails if the requested
 // address range cannot be reserved. Typically this would be because
 // it overlaps an existing, possibly system, mapping.
-bool InternalElfLoader::ReserveAddressSpace(Error* error) {
+bool InternalElfLoader::ReserveAddressSpace(const LoadParams& params,
+                                            Error* error) {
   ELF::Addr min_vaddr;
   load_size_ =
       phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr, NULL);
@@ -229,20 +226,25 @@
     return false;
   }
 
-  uint8_t* addr = NULL;
+  void* addr = nullptr;
   int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
 
   // Support loading at a fixed address.
-  if (wanted_load_address_) {
-    addr = static_cast<uint8_t*>(wanted_load_address_);
+  if (params.wanted_address) {
+    addr = reinterpret_cast<void*>(params.wanted_address);
+    mmap_flags |= MAP_FIXED;
   }
 
   size_t reserved_size = load_size_;
 
-  LOG("address=%p size=%p", addr, reserved_size);
+  LOG("Trying to reserve memory address=%p size=%lu (0x%lx)", addr,
+      static_cast<unsigned long>(load_size_),
+      static_cast<unsigned long>(load_size_));
+
   void* start = mmap(addr, reserved_size, PROT_NONE, mmap_flags, -1, 0);
   if (start == MAP_FAILED) {
-    error->Format("Could not reserve %d bytes of address space", reserved_size);
+    error->Format("Could not reserve %lu bytes of address space",
+                  static_cast<unsigned long>(reserved_size));
     return false;
   }
   if (addr && start != addr) {
@@ -253,12 +255,16 @@
 
   // Take ownership of the mapping here.
   reserved_map_ = MemoryMapping(start, reserved_size);
-  LOG("reserved start=%p", reserved_map_.address());
 
   load_start_ = start;
-  load_bias_ = reinterpret_cast<ELF::Addr>(start) - min_vaddr;
+  load_bias_ = reinterpret_cast<ELF::Addr>(load_start_) - min_vaddr;
 
-  LOG("load start=%p, bias=%p", load_start_, load_bias_);
+  LOG("Reserved memory address=%p, size=%lu (0x%lx), bias=%lu (0x%lx)",
+      load_start_, static_cast<unsigned long>(load_size_),
+      static_cast<unsigned long>(load_size_),
+      static_cast<unsigned long>(load_bias_),
+      static_cast<unsigned long>(load_bias_));
+
   return true;
 }
 
@@ -390,13 +396,10 @@
 }  // namespace
 
 // static
-ElfLoader::Result ElfLoader::LoadAt(const char* lib_path,
-                                    off_t file_offset,
-                                    uintptr_t wanted_address,
-                                    Error* error) {
+ElfLoader::Result ElfLoader::LoadAt(const LoadParams& params, Error* error) {
   InternalElfLoader loader;
   Result result;
-  if (loader.LoadAt(lib_path, file_offset, wanted_address, error)) {
+  if (loader.LoadAt(params, error)) {
     result.load_start = reinterpret_cast<ELF::Addr>(loader.load_start());
     result.load_size = loader.load_size();
     result.load_bias = loader.load_bias();
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.h b/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.h
index bf4016c..03924e0b 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.h
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_elf_loader.h
@@ -6,6 +6,7 @@
 #define CRAZY_LINKER_ELF_LOADER_H
 
 #include "crazy_linker_error.h"
+#include "crazy_linker_load_params.h"
 #include "crazy_linker_memory_mapping.h"
 #include "crazy_linker_system.h"  // For ScopedFileDescriptor
 #include "elf_traits.h"
@@ -29,32 +30,13 @@
     const ELF::Phdr* phdr = nullptr;
     size_t phdr_count = 0;
     MemoryMapping reserved_mapping;
-    Error error;  // empty in case of success.
 
     constexpr bool IsValid() const { return this->load_start != 0; }
   };
 
-  // Try to load a library at a given address. On failure, this will
-  // update the linker error message and returns false.
-  //
-  // |lib_path| is the full library path, and |wanted_address| should
-  // be the desired load address, or 0 to enable randomization.
-  //
-  // |file_offset| is an offset in the file where the ELF header will
-  // be looked for.
-  //
-  // |wanted_address| is the wanted load address (of the first loadable
-  // segment), or 0 to enable randomization.
-  //
-  // On success, returns a valid Result instance, where |reserved_mapping| will
-  // map the single range of reserved memory addresses for the ELF object
-  // (including the breakpad guard regions).
-  //
-  // On failure, return an invalid Result instance, and sets |*error|.
-  static Result LoadAt(const char* lib_path,
-                       off_t file_offset,
-                       uintptr_t wanted_address,
-                       Error* error);
+  // Try to load a library at a given address. On failure, return an
+  // invalid Result instance, and sets |*error|.
+  static Result LoadAt(const LoadParams& params, Error* error);
 };
 
 }  // namespace crazy
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_library_list.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_library_list.cpp
index 99d0a209..c7565a9 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_library_list.cpp
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_library_list.cpp
@@ -53,6 +53,29 @@
   }
 };
 
+// Checks that |params| is compatible with a system library load.
+// On success return true. On failure, set |*error| and return false.
+// |lib_name| is either the library name, or nullptr, in which case
+// |params.library_path| will be used for the error message.
+bool CheckSystemLibraryLoadParams(const char* lib_name,
+                                  const LoadParams& params,
+                                  Error* error) {
+  if (!lib_name)
+    lib_name = params.library_path.c_str();
+
+  if (params.library_offset != 0) {
+    error->Format("Cannot load system library from offset 0x%08lx: %s",
+                  static_cast<unsigned long>(params.library_offset), lib_name);
+    return false;
+  }
+  if (params.wanted_address != 0) {
+    error->Format("Cannot load system library at address 0x%08lx: %s",
+                  static_cast<unsigned long>(params.wanted_address), lib_name);
+    return false;
+  }
+  return true;
+}
+
 }  // namespace
 
 LibraryList::LibraryList() {
@@ -295,11 +318,8 @@
                                                       Error* error) {
   // First check whether a library with the same base name was
   // already loaded.
-  LibraryView* view = FindKnownLibrary(lib_name);
-  if (view) {
-    view->AddRef();
-    return view;
-  }
+  ASSERT(!FindKnownLibrary(lib_name),
+         "System library already loaded: ", lib_name);
 
   void* system_lib = SystemLinker::Open(lib_name, dlopen_mode);
   if (!system_lib) {
@@ -309,86 +329,120 @@
   }
 
   // Can't really find the DT_SONAME of this library, assume if is its basename.
-  view = new LibraryView(system_lib, GetBaseNamePtr(lib_name));
+  LibraryView* view = new LibraryView(system_lib, GetBaseNamePtr(lib_name));
   known_libraries_.PushBack(view);
 
   LOG("System library %s loaded at %p", lib_name, view);
   return view;
 }
 
-LibraryView* LibraryList::LoadLibrary(const char* lib_name,
-                                      uintptr_t load_address,
-                                      SearchPathList* search_path_list,
-                                      Error* error) {
-  const char* base_name = GetBaseNamePtr(lib_name);
+Expected<LibraryView*> LibraryList::FindAndCheckLoadedLibrary(
+    const char* lib_name,
+    const LoadParams& params,
+    Error* error) {
+  // First check whether a library with the same base name was already loaded.
+  LibraryView* view = FindKnownLibrary(lib_name);
+  if (!view)
+    return nullptr;
 
-  LOG("lib_name='%s'", lib_name);
-
-  // First check whether a library with the same base name was
-  // already loaded.
-  LibraryView* view = FindKnownLibrary(base_name);
-  if (view) {
-    if (load_address) {
+  if (view->IsSystem()) {
+    if (!CheckSystemLibraryLoadParams(lib_name, params, error))
+      return error;
+  } else {
+    const SharedLibrary* crazy_lib = view->GetCrazy();
+    ASSERT(crazy_lib != nullptr, "Not a crazy library");
+    if (params.wanted_address) {
       // Check that this is a crazy library and that is was loaded at
       // the correct address.
-      if (!view->IsCrazy()) {
-        error->Format("System library can't be loaded at fixed address %08x",
-                      load_address);
-        return nullptr;
-      }
-      uintptr_t actual_address = view->GetCrazy()->load_address();
-      if (actual_address != load_address) {
-        error->Format("Library already loaded at @%08x, can't load it at @%08x",
-                      actual_address,
-                      load_address);
-        return nullptr;
+      uintptr_t actual_address = crazy_lib->load_address();
+      if (actual_address != params.wanted_address) {
+        error->Format(
+            "Library already loaded at address 0x%08lx, can't load it at "
+            "0x%08lx: %s",
+            static_cast<unsigned long>(actual_address),
+            static_cast<unsigned long>(params.wanted_address), lib_name);
+        return error;
       }
     }
-    view->AddRef();
-    return view;
   }
 
-  // Find the full library path.
-  String full_path;
+  view->AddRef();
+  return view;
+}
 
+// static
+bool LibraryList::LocateLibraryFile(const char* lib_name,
+                                    const SearchPathList& search_path_list,
+                                    LoadParams* params,
+                                    Error* error) {
   LOG("Looking through the search path list");
-  SearchPathList::Result probe = search_path_list->FindFile(lib_name);
+  SearchPathList::Result probe = search_path_list.FindFile(lib_name);
   if (!probe.IsValid()) {
     error->Format("Can't find library file %s", lib_name);
-    return nullptr;
+    return false;
   }
   LOG("Found library: path %s @ 0x%x", probe.path.c_str(), probe.offset);
+  params->library_path = std::move(probe.path);
+  params->library_offset = probe.offset;
+  return true;
+}
 
-  if (IsSystemLibraryPath(probe.path.c_str())) {
-    return LoadLibraryWithSystemLinker(probe.path.c_str(), RTLD_NOW, error);
+LibraryView* LibraryList::LoadLibrary(const char* lib_name,
+                                      const LoadParams& params,
+                                      Error* error) {
+  Expected<LibraryView*> found =
+      FindAndCheckLoadedLibrary(lib_name, params, error);
+  if (!found.has_value())
+    return nullptr;
+  if (found.value())
+    return found.value();
+  return LoadLibraryInternal(params, error);
+}
+
+LibraryView* LibraryList::LoadLibraryInternal(const LoadParams& params,
+                                              Error* error) {
+  // Load the library with the system linker if necessary.
+  const char* lib_path = params.library_path.c_str();
+  if (IsSystemLibraryPath(lib_path)) {
+    if (!CheckSystemLibraryLoadParams(lib_path, params, error))
+      return nullptr;
+    return LoadLibraryWithSystemLinker(lib_path, RTLD_NOW, error);
   }
 
   // Load the library with the crazy linker.
   ScopedPtr<SharedLibrary> lib(new SharedLibrary());
-  if (!lib->Load(probe.path.c_str(), load_address, probe.offset, error))
+  if (!lib->Load(params, error))
     return nullptr;
 
   // Load all dependendent libraries.
+  const char* base_name = GetBaseNamePtr(lib_path);
   LOG("Loading dependencies of %s", base_name);
   SharedLibrary::DependencyIterator iter(lib.Get());
   Vector<LibraryView*> dependencies;
   while (iter.GetNext()) {
     Error dep_error;
-    // TODO(digit): Call LoadLibrary recursively instead when properly
-    // detecting system vs Chromium libraries (http://crbug.com/843987).
-    LibraryView* dependency =
-        LoadLibraryWithSystemLinker(iter.GetName(), RTLD_NOW, &dep_error);
+    // TODO(digit): Better library dependency loading that isn't limited
+    // to system libraries. This would allow the linker to load anything
+    // without the caller having to load all dependencies before hand in
+    // reverse topological order.
+    const char* dependency_name = iter.GetName();
+    LibraryView* dependency = FindKnownLibrary(dependency_name);
     if (!dependency) {
-      error->Format("When loading %s: %s", base_name, dep_error.c_str());
-      return nullptr;
+      dependency =
+          LoadLibraryWithSystemLinker(dependency_name, RTLD_NOW, &dep_error);
+      if (!dependency) {
+        error->Format("When loading %s: %s", base_name, dep_error.c_str());
+        // TODO(digit): Unload all dependencies that were loaded so far.
+        return nullptr;
+      }
     }
     dependencies.PushBack(dependency);
   }
   if (CRAZY_DEBUG) {
     LOG("Dependencies loaded for %s", base_name);
     for (const LibraryView* dep : dependencies)
-      LOG("  ... %p %s\n", dep, dep->GetName());
-    LOG("    dependencies @%p\n", &dependencies);
+      LOG("  ... %p %s", dep, dep->GetName());
+    LOG("    dependencies @%p", &dependencies);
   }
 
   // Relocate the library.
@@ -412,7 +466,7 @@
   head_ = lib.Get();
 
   // Then create a new LibraryView for it.
-  view = new LibraryView(lib.Release());
+  LibraryView* view = new LibraryView(lib.Release());
   known_libraries_.PushBack(view);
 
   LOG("Running constructors for %s", base_name);
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_library_list.h b/third_party/android_crazy_linker/src/src/crazy_linker_library_list.h
index 27e6f31..20a6ab6d7f 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_library_list.h
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_library_list.h
@@ -8,6 +8,8 @@
 #include <link.h>
 
 #include "crazy_linker_error.h"
+#include "crazy_linker_expected.h"
+#include "crazy_linker_load_params.h"
 #include "crazy_linker_search_path_list.h"
 #include "elf_traits.h"
 
@@ -62,13 +64,42 @@
   int IteratePhdr(PhdrIterationCallback callback, void* data);
 #endif
 
-  // Try to load a library, possibly at a fixed address.
-  // On failure, returns NULL and sets the |error| message.
-  LibraryView* LoadLibrary(const char* path,
-                           uintptr_t load_address,
-                           SearchPathList* search_path_list,
+  // Find whether a library identified by |name| has already been loaded.
+  // Note that |name| should correspond to the library's unique soname, which
+  // comes from its DT_SONAME entry, and typically, but not necessarily
+  // matches its base name.
+  LibraryView* FindKnownLibrary(const char* name);
+
+  // Check whether |lib_path| matches an already loaded library, compatible
+  // with the content of |load_params| (except its |library_path| field).
+  // On failure, i.e. if the load parameters are incompatible, set |*error|
+  // and return its address. On success, return either nullptr (if the library
+  // was not previously loaded, or a LibraryView* pointer after incrementing
+  // its reference count).
+  Expected<LibraryView*> FindAndCheckLoadedLibrary(
+      const char* lib_path,
+      const LoadParams& load_params,
+      Error* error);
+
+  // Locate library |lib_name| using |search_path_list|. On success, update
+  // |params->library_path| and |params->library_offset| and return true. On
+  // failure, set |*error| and return false.
+  static bool LocateLibraryFile(const char* lib_name,
+                                const SearchPathList& search_path_list,
+                                LoadParams* params,
+                                Error* error);
+
+  // Try to load a library, according to |load_params|. On failure, returns
+  // nullptr and sets the |error| message.
+  LibraryView* LoadLibrary(const char* lib_name,
+                           const LoadParams& load_params,
                            Error* error);
 
+  // Try to load a library, according to |load_params|.
+  // On failure, return nullptr and sets the |error| message.
+  // Note: this will fail if the library is already loaded.
+  LibraryView* LoadLibraryInternal(const LoadParams& load_params, Error* error);
+
   // Unload a given shared library. This really decrements the library's
   // internal reference count. When it reaches zero, the library's
   // destructors are run, its dependencies are unloaded, then the
@@ -103,12 +134,6 @@
   // The list of all known libraries.
   Vector<LibraryView*> known_libraries_;
 
-  // Find whether a library identified by |name| has already been loaded.
-  // Note that |name| should correspond to the library's unique soname, which
-  // comes from its DT_SONAME entry, and typically, but not necessarily
-  // matches its base name.
-  LibraryView* FindKnownLibrary(const char* name);
-
   // The list of all libraries loaded by the crazy linker.
   // This does _not_ include system libraries present in known_libraries_.
   SharedLibrary* head_ = nullptr;
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_load_params.h b/third_party/android_crazy_linker/src/src/crazy_linker_load_params.h
new file mode 100644
index 0000000..66cd03c
--- /dev/null
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_load_params.h
@@ -0,0 +1,32 @@
+// 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 CRAZY_LINKER_LOAD_PARAMS_H
+#define CRAZY_LINKER_LOAD_PARAMS_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "crazy_linker_util.h"
+
+namespace crazy {
+
+// A structure used to hold parameters related to loading an ELF library
+// into the current process' address space.
+//
+// |library_path| is either the full library path.
+// |library_offset| is the page-aligned offset where the library starts in
+// its input file (typically > 0 when reading from Android APKs).
+// |wanted_address| is either 0, or the address where the library should
+// be loaded.
+struct LoadParams {
+  String library_path;
+  off_t library_offset = 0;
+  uintptr_t wanted_address = 0;
+};
+
+}  // namespace crazy
+
+#endif  // CRAZY_LINKER_LOAD_PARAMS_H
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.cpp
index de136c4..1f5a711e 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.cpp
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.cpp
@@ -15,6 +15,7 @@
 #include "crazy_linker_globals.h"
 #include "crazy_linker_library_list.h"
 #include "crazy_linker_library_view.h"
+#include "crazy_linker_load_params.h"
 #include "crazy_linker_memory_mapping.h"
 #include "crazy_linker_system_linker.h"
 #include "crazy_linker_thread_data.h"
@@ -225,11 +226,9 @@
 
 SharedLibrary::~SharedLibrary() = default;
 
-bool SharedLibrary::Load(const char* full_path,
-                         size_t load_address,
-                         size_t file_offset,
-                         Error* error) {
+bool SharedLibrary::Load(const LoadParams& params, Error* error) {
   // First, record the path.
+  const char* full_path = params.library_path.c_str();
   LOG("full path '%s'", full_path);
 
   size_t full_path_len = strlen(full_path);
@@ -252,8 +251,7 @@
   LOG("Loading ELF segments for %s", base_name_);
 
   {
-    ElfLoader::Result ret =
-        ElfLoader::LoadAt(full_path_, file_offset, load_address, error);
+    ElfLoader::Result ret = ElfLoader::LoadAt(params, error);
     if (!ret.IsValid() ||
         !view_.InitUnmapped(ret.load_start, ret.phdr, ret.phdr_count, error)) {
       return false;
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.h b/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.h
index b0c7b89..5a32665 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.h
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_shared_library.h
@@ -13,6 +13,7 @@
 #include "crazy_linker_elf_symbols.h"
 #include "crazy_linker_elf_view.h"
 #include "crazy_linker_error.h"
+#include "crazy_linker_load_params.h"
 #include "crazy_linker_memory_mapping.h"
 #include "crazy_linker_rdebug.h"
 #include "crazy_linker_util.h"
@@ -50,17 +51,13 @@
   // Load a library (without its dependents) from an ELF file.
   // Note: This does not apply relocations, nor runs constructors.
   // |full_path| if the file full path.
-  // |load_address| is the page-aligned load address in memory, or 0.
-  // |file_offset| is the page-aligned file offset.
+  // |params| are the load parameters for this operation.
   // On failure, return false and set |error| message.
   //
   // After this, the caller should load all library dependencies,
   // Then call Relocate() and CallConstructors() to complete the
   // operation.
-  bool Load(const char* full_path,
-            size_t load_address,
-            size_t file_offset,
-            Error* error);
+  bool Load(const LoadParams& params, Error* error);
 
   // Relocate this library, assuming all its dependencies are already
   // loaded in |lib_list|. On failure, return false and set |error|
diff --git a/third_party/android_crazy_linker/src/src/crazy_linker_wrappers.cpp b/third_party/android_crazy_linker/src/src/crazy_linker_wrappers.cpp
index f25ffaea..44e54af 100644
--- a/third_party/android_crazy_linker/src/src/crazy_linker_wrappers.cpp
+++ b/third_party/android_crazy_linker/src/src/crazy_linker_wrappers.cpp
@@ -89,17 +89,26 @@
 
 void* WrapDlopen(const char* path, int mode) {
   ScopedLockedGlobals globals;
+  LibraryList* libs = globals->libraries();
 
   // NOTE: If |path| is NULL, the wrapper should return a handle
   // corresponding to the current executable. This can't be a crazy
   // library, so don't try to handle it with the crazy linker.
   if (path) {
     Error error;
-    LibraryView* wrap = globals->libraries()->LoadLibrary(
-        path, 0U /* load_address */, globals->search_path_list(), &error);
-    if (wrap) {
-      globals->valid_handles()->Add(wrap);
-      return wrap;
+    LibraryView* view = libs->FindKnownLibrary(path);
+    if (!view) {
+      LoadParams params;
+      if (libs->LocateLibraryFile(path, *globals->search_path_list(), &params,
+                                  &error)) {
+        view = libs->LoadLibraryInternal(params, &error);
+        if (!view) {
+          SetLinkerError("%s: %s", "dlopen", error.c_str());
+          return nullptr;
+        }
+        globals->valid_handles()->Add(view);
+        return view;
+      }
     }
   }
 
@@ -110,10 +119,10 @@
     return nullptr;
   }
 
-  auto* wrap_lib = new LibraryView(system_lib, path ? path : "<executable>");
-  globals->libraries()->AddLibrary(wrap_lib);
-  globals->valid_handles()->Add(wrap_lib);
-  return wrap_lib;
+  auto* view = new LibraryView(system_lib, path ? path : "<executable>");
+  libs->AddLibrary(view);
+  globals->valid_handles()->Add(view);
+  return view;
 }
 
 void* WrapDlsym(void* lib_handle, const char* symbol_name) {
diff --git a/third_party/blink/renderer/core/html/html_plugin_element.cc b/third_party/blink/renderer/core/html/html_plugin_element.cc
index 011671b..5edf9ef7 100644
--- a/third_party/blink/renderer/core/html/html_plugin_element.cc
+++ b/third_party/blink/renderer/core/html/html_plugin_element.cc
@@ -746,8 +746,7 @@
 }
 
 void HTMLPlugInElement::ReattachOnPluginChangeIfNeeded() {
-  if (UseFallbackContent() || !NeedsPluginUpdate() || !GetLayoutObject() ||
-      IsImageType())
+  if (UseFallbackContent() || !NeedsPluginUpdate() || !GetLayoutObject())
     return;
 
   SetNeedsStyleRecalc(
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index 084317d..7783479 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -22,6 +22,7 @@
 struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment {
   void* pointers[3];
   unsigned offsets[2];
+  PhysicalRect rect;
 };
 
 static_assert(sizeof(NGPhysicalTextFragment) ==
@@ -75,7 +76,7 @@
   DCHECK_GE(start_offset_, source.StartOffset());
   DCHECK_LE(end_offset_, source.EndOffset());
   DCHECK(shape_result_ || IsFlowControl()) << ToString();
-  DCHECK(!source.rare_data_ || !source.rare_data_->style_);
+  DCHECK(!source.style_);
   line_orientation_ = source.line_orientation_;
   is_anonymous_text_ = source.is_anonymous_text_;
   ink_overflow_computed_ = false;
@@ -92,7 +93,7 @@
       static_cast<unsigned>(ToLineOrientation(builder->GetWritingMode()));
 
   if (UNLIKELY(StyleVariant() == NGStyleVariant::kEllipsis)) {
-    EnsureRareData()->style_ = std::move(builder->style_);
+    style_ = std::move(builder->style_);
     is_anonymous_text_ = true;
   } else {
     is_anonymous_text_ =
@@ -102,21 +103,14 @@
   ink_overflow_computed_ = false;
 }
 
-NGPhysicalTextFragment::RareData* NGPhysicalTextFragment::EnsureRareData()
-    const {
-  if (!rare_data_)
-    rare_data_ = std::make_unique<RareData>();
-  return rare_data_.get();
-}
-
 const ComputedStyle& NGPhysicalTextFragment::Style() const {
   switch (StyleVariant()) {
     case NGStyleVariant::kStandard:
     case NGStyleVariant::kFirstLine:
       return NGPhysicalFragment::Style();
     case NGStyleVariant::kEllipsis:
-      DCHECK(rare_data_ && rare_data_->style_);
-      return *rare_data_->style_;
+      DCHECK(style_);
+      return *style_;
   }
   NOTREACHED();
   return NGPhysicalFragment::Style();
@@ -213,12 +207,11 @@
 PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const {
   if (!ink_overflow_computed_)
     ComputeSelfInkOverflow();
-  return UNLIKELY(rare_data_) ? rare_data_->self_ink_overflow_ : LocalRect();
+  return self_ink_overflow_;
 }
 
 void NGPhysicalTextFragment::ClearSelfInkOverflow() const {
-  if (UNLIKELY(rare_data_))
-    rare_data_->self_ink_overflow_ = LocalRect();
+  self_ink_overflow_ = LocalRect();
 }
 
 void NGPhysicalTextFragment::ComputeSelfInkOverflow() const {
@@ -275,12 +268,12 @@
   PhysicalRect local_ink_overflow = ConvertToLocal(ink_overflow);
   PhysicalRect local_rect = LocalRect();
   if (local_rect.Contains(local_ink_overflow)) {
-    ClearSelfInkOverflow();
+    self_ink_overflow_ = local_rect;
     return;
   }
   local_ink_overflow.Unite(local_rect);
   local_ink_overflow.ExpandEdgesToPixelBoundaries();
-  EnsureRareData()->self_ink_overflow_ = local_ink_overflow;
+  self_ink_overflow_ = local_ink_overflow;
 }
 
 scoped_refptr<const NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
index e7d5ddb..a4778e7 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
@@ -142,15 +142,6 @@
                          unsigned end_offset,
                          scoped_refptr<const ShapeResultView> shape_result);
 
-  struct RareData {
-    USING_FAST_MALLOC(RareData);
-
-   public:
-    PhysicalRect self_ink_overflow_;
-    scoped_refptr<const ComputedStyle> style_;  // Used only for ellipsis.
-  };
-  RareData* EnsureRareData() const;
-
   LayoutUnit InlinePositionForOffset(unsigned offset,
                                      LayoutUnit (*round)(float),
                                      AdjustMidCluster) const;
@@ -172,7 +163,8 @@
   // Fragments are immutable but allow certain expensive data, specifically ink
   // overflow, to be cached as long as it is guaranteed to always recompute to
   // the same value.
-  mutable std::unique_ptr<RareData> rare_data_;
+  mutable PhysicalRect self_ink_overflow_;
+  scoped_refptr<const ComputedStyle> style_;  // Used only for ellipsis.
 };
 
 template <>
diff --git a/third_party/blink/renderer/devtools/front_end/performance_monitor/PerformanceMonitor.js b/third_party/blink/renderer/devtools/front_end/performance_monitor/PerformanceMonitor.js
index 393bf788..500e189 100644
--- a/third_party/blink/renderer/devtools/front_end/performance_monitor/PerformanceMonitor.js
+++ b/third_party/blink/renderer/devtools/front_end/performance_monitor/PerformanceMonitor.js
@@ -157,10 +157,10 @@
       if (x < -50)
         break;
       ctx.beginPath();
-      ctx.moveTo(Math.round(x) + 0.5, 0);
-      ctx.lineTo(Math.round(x) + 0.5, this._height);
+      ctx.moveTo(x, 0);
+      ctx.lineTo(x, this._height);
       if (sec >= 0 && sec % labelDistanceSeconds === 0)
-        ctx.fillText(new Date(sec * 1000).toLocaleTimeString(), Math.round(x) + 4, 12);
+        ctx.fillText(new Date(sec * 1000).toLocaleTimeString(), x + 4, 12);
       ctx.strokeStyle = sec % labelDistanceSeconds ? lightGray : this._gridColor;
       ctx.stroke();
     }
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/ProfileLauncherView.js b/third_party/blink/renderer/devtools/front_end/profiler/ProfileLauncherView.js
index b78bc66..f0787a717 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/ProfileLauncherView.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/ProfileLauncherView.js
@@ -41,28 +41,26 @@
 
     this._panel = profilesPanel;
     this.element.classList.add('profile-launcher-view');
+    this._contentElement = this.element.createChild('div', 'profile-launcher-view-content vbox');
 
-    const isolateSelector = new Profiler.IsolateSelector();
-    this._contentElement = this.element.createChild('div', 'profile-launcher-view-content');
-    this._innerContentElement = this._contentElement.createChild('div');
-    const controlDiv = this._contentElement.createChild('div', 'vbox profile-launcher-control');
-    controlDiv.createChild('h1').textContent = ls`Select JavaScript VM instance`;
-    controlDiv.appendChild(isolateSelector.totalMemoryElement());
-    const targetDiv = controlDiv.createChild('div', 'vbox profile-launcher-target-list');
-    isolateSelector.show(targetDiv);
-
-    this._controlButton =
-        UI.createTextButton('', this._controlButtonClicked.bind(this), 'profile-launcher-button', true /* primary */);
-    this._contentElement.appendChild(this._controlButton);
-    this._recordButtonEnabled = true;
-    this._loadButton =
-        UI.createTextButton(Common.UIString('Load'), this._loadButtonClicked.bind(this), 'profile-launcher-button');
-    this._contentElement.appendChild(this._loadButton);
-
+    const profileTypeSelectorElement = this._contentElement.createChild('div', 'vbox');
     this._selectedProfileTypeSetting = Common.settings.createSetting('selectedProfileType', 'CPU');
-    this._header = this._innerContentElement.createChild('h1');
-    this._profileTypeSelectorForm = this._innerContentElement.createChild('form');
-    this._innerContentElement.createChild('div', 'flexible-space');
+    this._profileTypeHeaderElement = profileTypeSelectorElement.createChild('h1');
+    this._profileTypeSelectorForm = profileTypeSelectorElement.createChild('form');
+
+    const isolateSelectorElement = this._contentElement.createChild('div', 'vbox profile-isolate-selector-block');
+    isolateSelectorElement.createChild('h1').textContent = ls`Select JavaScript VM instance`;
+    const isolateSelector = new Profiler.IsolateSelector();
+    isolateSelector.show(isolateSelectorElement.createChild('div', 'vbox profile-launcher-target-list'));
+    isolateSelectorElement.appendChild(isolateSelector.totalMemoryElement());
+
+    const buttonsDiv = this._contentElement.createChild('div', 'hbox profile-launcher-buttons');
+    this._controlButton = UI.createTextButton('', this._controlButtonClicked.bind(this), '', /* primary */ true);
+    this._loadButton = UI.createTextButton(ls`Load`, this._loadButtonClicked.bind(this), '');
+    buttonsDiv.appendChild(this._controlButton);
+    buttonsDiv.appendChild(this._loadButton);
+    this._recordButtonEnabled = true;
+
     /** @type {!Map<string, !HTMLOptionElement>} */
     this._typeIdToOptionElement = new Map();
   }
@@ -132,9 +130,9 @@
     if (customContent)
       this._profileTypeSelectorForm.createChild('p').appendChild(customContent);
     if (this._typeIdToOptionElement.size > 1)
-      this._header.textContent = ls`Select profiling type`;
+      this._profileTypeHeaderElement.textContent = ls`Select profiling type`;
     else
-      this._header.textContent = profileType.name;
+      this._profileTypeHeaderElement.textContent = profileType.name;
   }
 
   restoreSelectedProfileType() {
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css b/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
index c73652a4..24824af 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
+++ b/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
@@ -4,10 +4,69 @@
  * found in the LICENSE file.
  */
 
+.profile-launcher-view {
+    overflow: auto;
+}
+
+.profile-launcher-view-content {
+    margin: 10px 16px;
+    flex: auto 1 0;
+}
+
+.profile-launcher-view-content h1 {
+    font-size: 15px;
+    font-weight: normal;
+    margin: 6px 0 10px 0;
+}
+
+.profile-launcher-view-content [is=dt-radio] {
+    font-size: 13px;
+}
+
+.profile-launcher-view-content p {
+    color: grey;
+    margin-top: 1px;
+    margin-left: 22px;
+}
+
+.profile-launcher-view-content p [is=dt-checkbox] {
+    display: flex;
+}
+
+.profile-launcher-view-content button.running {
+    color: hsl(0, 100%, 58%);
+}
+
+.profile-launcher-view-content button.running:hover {
+    color: hsl(0, 100%, 42%);
+}
+
+body.inactive .profile-launcher-view-content button.running:not(.toolbar-item) {
+    color: rgb(220, 130, 130);
+}
+
+.profile-launcher-view-content > div {
+    flex: auto 0 0;
+}
+
+.profile-launcher-view-content > .profile-isolate-selector-block {
+    flex: auto 1 0;
+}
+
+.profile-isolate-selector-block button {
+    min-width: 110px;
+}
+
+.profile-launcher-target-list {
+    margin-bottom: 6px;
+    border: 1px solid #ddd;
+    flex: 150px 1 0;
+}
+
 .profile-memory-usage-item {
     padding: 4px;
     line-height: 16px;
-    border-left: 2px solid transparent;
+    border-left: 3px solid transparent;
     overflow-x: hidden;
 }
 
@@ -39,7 +98,18 @@
     color: red;
 }
 
-.profile-launcher-control > .profile-memory-usage-item {
+.profile-isolate-selector-block > .profile-memory-usage-item {
     margin-left: 1px;
-    margin-bottom: 6px;
+    margin-bottom: 4px;
+    font-weight: bolder;
+}
+
+.profile-launcher-buttons {
+    flex-wrap: wrap;
+}
+
+.profile-launcher-buttons button {
+    min-width: 120px;
+    height: 28px;
+    margin: 4px 16px 4px 0;
 }
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/profilesPanel.css b/third_party/blink/renderer/devtools/front_end/profiler/profilesPanel.css
index 4849d7b..4fcbd334 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/profilesPanel.css
+++ b/third_party/blink/renderer/devtools/front_end/profiler/profilesPanel.css
@@ -112,75 +112,6 @@
     color: white;
 }
 
-
-.profile-launcher-view {
-    overflow: auto;
-}
-
-.profile-launcher-view-content {
-    padding: 10px 16px;
-}
-
-.profile-launcher-view-content h1 {
-    font-size: 15px;
-    font-weight: normal;
-    padding: 6px 0;
-    margin: 0 0 5px 0;
-}
-
-.profile-launcher-view-content [is=dt-radio] {
-    font-size: 13px;
-}
-
-.profile-launcher-control {
-    align-items: flex-start;
-    flex-wrap: wrap;
-    margin-top: 10px;
-    margin-right: 6px;
-}
-
-.profile-launcher-control button {
-    min-width: 110px;
-}
-
-.profile-launcher-target-list {
-    width: 100%;
-    height: 150px;
-    margin-bottom: 10px;
-    border: 1px solid #ddd;
-}
-
-.profile-launcher-target {
-    align-items: baseline;
-}
-
-.profile-launcher-target > * {
-    flex: 0 0 auto;
-    margin-right: 8px;
-}
-
-.profile-launcher-view-content p {
-    color: grey;
-    margin-top: 1px;
-    margin-left: 22px;
-}
-
-.profile-launcher-view-content p [is=dt-checkbox] {
-    display: flex;
-}
-
-.profile-launcher-view-content button.running {
-    color: hsl(0, 100%, 58%);
-}
-
-.profile-launcher-view-content button.running:hover {
-    color: hsl(0, 100%, 42%);
-}
-
-body.inactive .profile-launcher-view-content button.running:not(.toolbar-item) {
-    color: rgb(220, 130, 130);
-}
-
 .highlighted-row {
     -webkit-animation: row_highlight 2s 0s;
 }
@@ -204,12 +135,6 @@
     margin: 0 0 0 10px !important;
 }
 
-.profile-launcher-button {
-    min-width: 120px;
-    height: 28px;
-    margin: 8px 16px 8px 0;
-}
-
 .cpu-profile-flame-chart-overview-container {
     overflow: hidden;
     position: absolute;
diff --git a/third_party/blink/renderer/devtools/front_end/ui/SoftDropDown.js b/third_party/blink/renderer/devtools/front_end/ui/SoftDropDown.js
index 6d21c347..799b2e2 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/SoftDropDown.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/SoftDropDown.js
@@ -15,6 +15,8 @@
     this._selectedItem = null;
     this._model = model;
 
+    this._placeholderText = ls`(no item selected)`;
+
     this.element = createElementWithClass('button', 'soft-dropdown');
     UI.appendStyle(this.element, 'ui/softDropDownButton.css');
     this._titleElement = this.element.createChild('span', 'title');
@@ -183,6 +185,15 @@
   }
 
   /**
+   * @param {string} text
+   */
+  setPlaceholderText(text) {
+    this._placeholderText = text;
+    if (!this._selectedItem)
+      this._titleElement.textContent = this._placeholderText;
+  }
+
+  /**
    * @param {!Common.Event} event
    */
   _itemsReplaced(event) {
@@ -202,7 +213,7 @@
     if (this._selectedItem)
       this._titleElement.textContent = this._delegate.titleFor(this._selectedItem);
     else
-      this._titleElement.textContent = '';
+      this._titleElement.textContent = this._placeholderText;
     this._delegate.itemSelected(this._selectedItem);
   }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_ray.idl b/third_party/blink/renderer/modules/xr/xr_ray.idl
index 3bdfaa9..fa85593 100644
--- a/third_party/blink/renderer/modules/xr/xr_ray.idl
+++ b/third_party/blink/renderer/modules/xr/xr_ray.idl
@@ -6,7 +6,7 @@
 [
     SecureContext,
     Exposed=Window,
-    RuntimeEnabled=WebXR,
+    RuntimeEnabled=WebXRHitTest,
     Constructor(),
     Constructor(DOMPointInit origin),
     Constructor(DOMPointInit origin, DOMPointInit direction),
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index f1b75535..695207e6 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -5,6 +5,8 @@
 #include "third_party/blink/renderer/platform/graphics/canvas_resource.h"
 
 #include <utility>
+
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/single_release_callback.h"
@@ -561,21 +563,20 @@
     SkFilterQuality filter_quality)
     : CanvasResource(std::move(provider), filter_quality, color_params),
       size_(size) {
-  shared_memory_ = viz::bitmap_allocation::AllocateMappedBitmap(
+  base::MappedReadOnlyRegion shm = viz::bitmap_allocation::AllocateSharedBitmap(
       gfx::Size(Size()), ColorParams().TransferableResourceFormat());
 
-  if (!IsValid())
+  if (!shm.IsValid())
     return;
 
+  shared_mapping_ = std::move(shm.mapping);
   shared_bitmap_id_ = viz::SharedBitmap::GenerateId();
 
   CanvasResourceDispatcher* resource_dispatcher =
       Provider() ? Provider()->ResourceDispatcher() : nullptr;
   if (resource_dispatcher) {
     resource_dispatcher->DidAllocateSharedBitmap(
-        viz::bitmap_allocation::DuplicateAndCloseMappedBitmap(
-            shared_memory_.get(), gfx::Size(Size()),
-            ColorParams().TransferableResourceFormat()),
+        viz::bitmap_allocation::ToMojoHandle(std::move(shm.region)),
         SharedBitmapIdToGpuMailboxPtr(shared_bitmap_id_));
   }
 }
@@ -585,7 +586,7 @@
 }
 
 bool CanvasResourceSharedBitmap::IsValid() const {
-  return !!shared_memory_;
+  return shared_mapping_.IsValid();
 }
 
 IntSize CanvasResourceSharedBitmap::Size() const {
@@ -602,7 +603,7 @@
   SkImageInfo image_info = SkImageInfo::Make(
       Size().Width(), Size().Height(), ColorParams().GetSkColorType(),
       ColorParams().GetSkAlphaType(), ColorParams().GetSkColorSpace());
-  SkPixmap pixmap(image_info, shared_memory_->memory(),
+  SkPixmap pixmap(image_info, shared_mapping_.memory(),
                   image_info.minRowBytes());
   this->AddRef();
   sk_sp<SkImage> sk_image = SkImage::MakeFromRaster(
@@ -633,11 +634,11 @@
     resource_dispatcher->DidDeleteSharedBitmap(
         SharedBitmapIdToGpuMailboxPtr(shared_bitmap_id_));
   }
-  shared_memory_ = nullptr;
+  shared_mapping_ = {};
 }
 
 void CanvasResourceSharedBitmap::Abandon() {
-  shared_memory_ = nullptr;
+  shared_mapping_ = {};
 }
 
 const gpu::Mailbox& CanvasResourceSharedBitmap::GetOrCreateGpuMailbox(
@@ -656,7 +657,7 @@
       ColorParams().GetSkColorSpaceForSkSurfaces());
 
   bool read_pixels_successful = image->readPixels(
-      image_info, shared_memory_->memory(), image_info.minRowBytes(), 0, 0);
+      image_info, shared_mapping_.memory(), image_info.minRowBytes(), 0, 0);
   DCHECK(read_pixels_successful);
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.h b/third_party/blink/renderer/platform/graphics/canvas_resource.h
index 38af9f0..cc98f5c1 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.h
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/logging.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "components/viz/common/resources/shared_bitmap.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
@@ -24,12 +25,6 @@
 
 }  // namespace gfx
 
-namespace base {
-
-class SharedMemory;
-
-}  // namespace base
-
 namespace viz {
 
 class SingleReleaseCallback;
@@ -305,7 +300,7 @@
                              SkFilterQuality);
 
   viz::SharedBitmapId shared_bitmap_id_;
-  std::unique_ptr<base::SharedMemory> shared_memory_;
+  base::WritableSharedMemoryMapping shared_mapping_;
   IntSize size_;
   bool is_origin_clean_ = true;
 };
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 1fdda6c5..77da370 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -1132,7 +1132,12 @@
       blink_effects[effect.id] &&
       blink_effects[effect.id]->HasActiveOpacityAnimation())
     return cc::RenderSurfaceReason::kOpacityAnimation;
-  if (effect.is_fast_rounded_corner)
+  // Applying a rounded corner clip to more than one layer descendant
+  // with highest quality requires a render surface, due to the possibility
+  // of antialiasing issues on the rounded corner edges.
+  // is_fast_rounded_corner means to intentionally prefer faster compositing
+  // and less memory over highest quality.
+  if (!effect.rounded_corner_bounds.IsEmpty() && !effect.is_fast_rounded_corner)
     return cc::RenderSurfaceReason::kRoundedCorner;
   return cc::RenderSurfaceReason::kNone;
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index cef9e09..c712eb03 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -2586,8 +2586,7 @@
 
   if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
     // Expectation in effect stack diagram:
-    //           l1
-    // l0
+    //          l0
     // [ mask_isolation_0 ]
     // [        e0        ]
     // One content layer.
@@ -2615,6 +2614,7 @@
     EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_0.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
     return;
   }
 
@@ -2644,6 +2644,7 @@
       *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
   ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
   EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+  EXPECT_TRUE(mask_isolation_0.HasRenderSurface());
 
   EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
   EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
@@ -2659,6 +2660,111 @@
   EXPECT_TRUE(clip_mask0->DrawsContent());
 }
 
+TEST_P(PaintArtifactCompositorTest, SynthesizedClipNested) {
+  // This tests the simplist case that a single layer needs to be clipped
+  // by a single composited rounded clip.
+  if (!RuntimeEnabledFeatures::FastBorderRadiusEnabled())
+    return;
+
+  FloatSize corner(5, 5);
+  FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
+                         corner);
+  auto c1 = CreateClip(c0(), t0(), rrect);
+  auto c2 = CreateClip(*c1, t0(), rrect);
+  auto c3 = CreateClip(*c2, t0(), rrect);
+  auto t1 = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
+                            CompositingReason::kWillChangeTransform);
+  CompositorFilterOperations filter_operations;
+  filter_operations.AppendBlurFilter(5);
+  auto filter = CreateFilterEffect(e0(), t0(), c1.get(), filter_operations);
+
+  TestPaintArtifact artifact;
+  artifact.Chunk(t0(), *c1, *filter)
+      .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+  artifact.Chunk(*t1, *c3, *filter)
+      .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
+  Update(artifact.Build());
+
+  // Expectation in effect stack diagram:
+  //            l0
+  //    [ mask_isolation_2 ]
+  // l1 [ mask_isolation_1 ]
+  // [       filter        ]
+  // [  mask_isolation_0   ]
+  // [         e0          ]
+  // Two content layers.
+  ///
+  // mask_isolation_1 will have a render surface. mask_isolation_2 will not
+  // because non-leaf synthetic rounded clips must have a render surface.
+  // mask_isolation_0 will not because it is a leaf synthetic rounded clip
+  // in the render surface created by the filter.
+
+  ASSERT_EQ(2u, RootLayer()->children().size());
+  ASSERT_EQ(2u, ContentLayerCount());
+  // There is still a "synthesized layer" but it's null.
+  ASSERT_EQ(3u, SynthesizedClipLayerCount());
+  EXPECT_FALSE(SynthesizedClipLayerAt(0));
+  EXPECT_FALSE(SynthesizedClipLayerAt(1));
+  EXPECT_FALSE(SynthesizedClipLayerAt(2));
+
+  const cc::Layer* content0 = RootLayer()->children()[0].get();
+  EXPECT_EQ(ContentLayerAt(0), content0);
+  const cc::Layer* content1 = RootLayer()->children()[1].get();
+  EXPECT_EQ(ContentLayerAt(1), content1);
+
+  constexpr int c0_id = 1;
+  constexpr int c1_id = 2;
+  constexpr int e0_id = 1;
+  constexpr int e1_id = 2;
+
+  int c3_id = content1->clip_tree_index();
+  const cc::ClipNode& cc_c3 = *GetPropertyTrees().clip_tree.Node(c3_id);
+  EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c3.clip);
+  const cc::ClipNode& cc_c2 =
+      *GetPropertyTrees().clip_tree.Node(cc_c3.parent_id);
+  EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c2.clip);
+  ASSERT_EQ(c1_id, cc_c2.parent_id);
+  const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
+  EXPECT_EQ(c1_id, content0->clip_tree_index());
+  EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
+  ASSERT_EQ(c0_id, cc_c1.parent_id);
+
+  int mask_isolation_2_id = content1->effect_tree_index();
+  const cc::EffectNode& mask_isolation_2 =
+      *GetPropertyTrees().effect_tree.Node(mask_isolation_2_id);
+  const cc::EffectNode& mask_isolation_1 =
+      *GetPropertyTrees().effect_tree.Node(mask_isolation_2.parent_id);
+  const cc::EffectNode& cc_filter =
+      *GetPropertyTrees().effect_tree.Node(mask_isolation_1.parent_id);
+  const cc::EffectNode& mask_isolation_0 =
+      *GetPropertyTrees().effect_tree.Node(cc_filter.parent_id);
+
+  ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
+  EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+  EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
+  EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
+            mask_isolation_0.rounded_corner_bounds);
+  EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
+
+  ASSERT_EQ(e1_id, cc_filter.parent_id);
+  EXPECT_EQ(cc_filter.id, content0->effect_tree_index());
+  EXPECT_EQ(SkBlendMode::kSrcOver, cc_filter.blend_mode);
+  EXPECT_FALSE(cc_filter.is_fast_rounded_corner);
+  EXPECT_TRUE(cc_filter.HasRenderSurface());
+
+  EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_1.blend_mode);
+  EXPECT_TRUE(mask_isolation_1.is_fast_rounded_corner);
+  EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
+            mask_isolation_1.rounded_corner_bounds);
+  EXPECT_TRUE(mask_isolation_1.HasRenderSurface());
+
+  EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_2.blend_mode);
+  EXPECT_TRUE(mask_isolation_2.is_fast_rounded_corner);
+  EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
+            mask_isolation_2.rounded_corner_bounds);
+  EXPECT_FALSE(mask_isolation_2.HasRenderSurface());
+}
+
 TEST_P(PaintArtifactCompositorTest, SynthesizedClipIsNotDrawable) {
   // This tests the simplist case that a single layer needs to be clipped
   // by a single composited rounded clip.
@@ -2794,6 +2900,7 @@
     EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 0),
               mask_isolation_0.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
     return;
   }
 
@@ -2900,6 +3007,7 @@
     EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_0.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
     return;
   }
 
@@ -3009,6 +3117,7 @@
     EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_0.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
 
     EXPECT_EQ(ContentLayerAt(1), content1);
     int t1_id = content1->transform_tree_index();
@@ -3030,6 +3139,7 @@
     EXPECT_TRUE(mask_isolation_1.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_1.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
     return;
   }
 
@@ -3064,6 +3174,7 @@
       *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
   ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
   EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+  EXPECT_TRUE(mask_isolation_0.HasRenderSurface());
 
   EXPECT_EQ(SynthesizedClipLayerAt(0), clip_mask0);
   EXPECT_EQ(gfx::Size(300, 200), clip_mask0->bounds());
@@ -3092,6 +3203,7 @@
   EXPECT_NE(mask_isolation_0_id, mask_isolation_1_id);
   ASSERT_EQ(e0_id, mask_isolation_1.parent_id);
   EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_1.blend_mode);
+  EXPECT_TRUE(mask_isolation_1.HasRenderSurface());
 
   EXPECT_EQ(SynthesizedClipLayerAt(1), clip_mask1);
   EXPECT_EQ(gfx::Size(300, 200), clip_mask1->bounds());
@@ -3153,6 +3265,7 @@
         *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
     ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
     EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
 
     EXPECT_EQ(ContentLayerAt(1), content1);
     EXPECT_EQ(c1_id, content1->clip_tree_index());
@@ -3169,7 +3282,6 @@
     EXPECT_TRUE(cc_e2.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_0.rounded_corner_bounds);
-
     return;
   }
 
@@ -3201,6 +3313,7 @@
       *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
   ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
   EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
+  EXPECT_TRUE(mask_isolation_0.HasRenderSurface());
 
   EXPECT_EQ(ContentLayerAt(1), content1);
   EXPECT_EQ(c1_id, content1->clip_tree_index());
@@ -3281,6 +3394,7 @@
     EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_0.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
 
     EXPECT_EQ(ContentLayerAt(1), content1);
     EXPECT_EQ(c1_id, content1->clip_tree_index());
@@ -3295,6 +3409,7 @@
     EXPECT_TRUE(mask_isolation_1.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_1.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_1.HasRenderSurface());
 
     EXPECT_EQ(ContentLayerAt(2), content2);
     EXPECT_EQ(c1_id, content2->clip_tree_index());
@@ -3308,6 +3423,7 @@
     EXPECT_TRUE(mask_isolation_2.is_fast_rounded_corner);
     EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 5),
               mask_isolation_2.rounded_corner_bounds);
+    EXPECT_FALSE(mask_isolation_2.HasRenderSurface());
     return;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 1006dac..98c3bd1 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -313,12 +313,18 @@
   current_.clip = &clip;
 
   if (cc_effect_node.HasRenderSurface()) {
-    current_.may_be_2d_axis_misaligned_to_render_surface = 0;
-  } else if (previous_transform &&
-             !current_.may_be_2d_axis_misaligned_to_render_surface) {
-    current_.may_be_2d_axis_misaligned_to_render_surface |=
-        TransformsMayBe2dAxisMisaligned(*previous_transform,
-                                        current_.Transform());
+    current_.may_be_2d_axis_misaligned_to_render_surface =
+        EffectState::kAligned;
+    current_.contained_by_non_render_surface_synthetic_rounded_clip = false;
+  } else {
+    if (current_.may_be_2d_axis_misaligned_to_render_surface ==
+            EffectState::kAligned &&
+        previous_transform != &current_.Transform()) {
+      current_.may_be_2d_axis_misaligned_to_render_surface =
+          EffectState::kUnknown;
+    }
+    current_.contained_by_non_render_surface_synthetic_rounded_clip |=
+        (effect_type & CcEffectType::kSyntheticForNonTrivialClip);
   }
 }
 
@@ -750,21 +756,58 @@
   return false;
 }
 
+bool PropertyTreeManager::EffectStateMayBe2dAxisMisalignedToRenderSurface(
+    EffectState& state,
+    size_t index) {
+  if (state.may_be_2d_axis_misaligned_to_render_surface ==
+      EffectState::kUnknown) {
+    // The root effect has render surface, so it's always kAligned.
+    DCHECK_NE(0u, index);
+    if (EffectStateMayBe2dAxisMisalignedToRenderSurface(
+            effect_stack_[index - 1], index - 1)) {
+      state.may_be_2d_axis_misaligned_to_render_surface =
+          EffectState::kMisaligned;
+    } else {
+      state.may_be_2d_axis_misaligned_to_render_surface =
+          TransformsMayBe2dAxisMisaligned(effect_stack_[index - 1].Transform(),
+                                          current_.Transform())
+              ? EffectState::kMisaligned
+              : EffectState::kAligned;
+    }
+  }
+  return state.may_be_2d_axis_misaligned_to_render_surface ==
+         EffectState::kMisaligned;
+}
+
+bool PropertyTreeManager::CurrentEffectMayBe2dAxisMisalignedToRenderSurface() {
+  // |current_| is virtually the top of effect_stack_.
+  return EffectStateMayBe2dAxisMisalignedToRenderSurface(current_,
+                                                         effect_stack_.size());
+}
+
 PropertyTreeManager::CcEffectType PropertyTreeManager::SyntheticEffectType(
-    const ClipPaintPropertyNode& clip) const {
+    const ClipPaintPropertyNode& clip) {
   unsigned effect_type = CcEffectType::kEffect;
   if (clip.ClipRect().IsRounded() || clip.ClipPath())
     effect_type |= CcEffectType::kSyntheticForNonTrivialClip;
 
   // Cc requires that a rectangluar clip is 2d-axis-aligned with the render
   // surface to correctly apply the clip.
-  if (current_.may_be_2d_axis_misaligned_to_render_surface |
+  if (CurrentEffectMayBe2dAxisMisalignedToRenderSurface() ||
       TransformsMayBe2dAxisMisaligned(clip.LocalTransformSpace(),
                                       current_.Transform()))
     effect_type |= CcEffectType::kSyntheticFor2dAxisAlignment;
   return static_cast<CcEffectType>(effect_type);
 }
 
+void PropertyTreeManager::ForceRenderSurfaceIfSyntheticRoundedCornerClip(
+    PropertyTreeManager::EffectState& state) {
+  if (state.effect_type & CcEffectType::kSyntheticForNonTrivialClip) {
+    auto& effect_node = *GetEffectTree().Node(state.effect_id);
+    effect_node.render_surface_reason = cc::RenderSurfaceReason::kRoundedCorner;
+  }
+}
+
 SkBlendMode PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
     const ClipPaintPropertyNode& target_clip_arg,
     SkBlendMode delegated_blend) {
@@ -856,6 +899,20 @@
         synthetic_effect.rounded_corner_bounds =
             gfx::RRectF(pending_clip.clip->ClipRect());
         synthetic_effect.is_fast_rounded_corner = true;
+
+        // Nested rounded corner clips need to force render surfaces for
+        // clips other than the leaf ones, because the compositor doesn't
+        // know how to apply two rounded clips to the same draw quad.
+        if (current_.contained_by_non_render_surface_synthetic_rounded_clip) {
+          ForceRenderSurfaceIfSyntheticRoundedCornerClip(current_);
+          for (auto effect_it = effect_stack_.rbegin();
+               effect_it != effect_stack_.rend(); ++effect_it) {
+            auto& effect_node = *GetEffectTree().Node(effect_it->effect_id);
+            if (effect_node.HasRenderSurface())
+              break;
+            ForceRenderSurfaceIfSyntheticRoundedCornerClip(*effect_it);
+          }
+        }
       } else {
         synthetic_effect.render_surface_reason =
             pending_clip.clip->ClipRect().IsRounded()
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
index ad38a2a..efe9f1c 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
@@ -140,7 +140,71 @@
   void SetupRootEffectNode();
   void SetupRootScrollNode();
 
+  // The type of operation the current cc effect node applies.
+  enum CcEffectType {
+    // The cc effect corresponds to a Blink effect node.
+    kEffect = 0,
+    // The cc effect is synthetic for a blink clip node that has to be
+    // rasterized because the clip is non-trivial.
+    kSyntheticForNonTrivialClip = 1 << 0,
+    // The cc effect is synthetic to create a render surface that is
+    // 2d-axis-aligned with a blink clip node that is non-2d-axis-aligned
+    // in the the original render surface. Cc requires a rectangular clip to be
+    // 2d-axis-aligned with the render surface to correctly apply the clip.
+    // TODO(crbug.com/504464): This will be changed when we move render surface
+    // decision logic into the cc compositor thread.
+    kSyntheticFor2dAxisAlignment = 1 << 1
+  };
+
+  struct EffectState {
+    // The cc effect node that has the corresponding drawing state to the
+    // effect and clip state from the last
+    // SwitchToEffectNodeWithSynthesizedClip.
+    int effect_id;
+
+    CcEffectType effect_type;
+
+    // The effect state of the cc effect node. It's never nullptr.
+    const EffectPaintPropertyNode* effect;
+
+    // The clip state of the cc effect node. This value may be shallower than
+    // the one passed into SwitchToEffectNodeWithSynthesizedClip because not
+    // every clip needs to be synthesized as cc effect. Is set to output clip of
+    // the effect if the type is kEffect, or set to the synthesized clip node.
+    // It's never nullptr.
+    const ClipPaintPropertyNode* clip;
+
+    // Whether the transform space of this state may be 2d axis misaligned to
+    // the containing render surface. As there may be new render surfaces
+    // created between this state and the current known ancestor render surface
+    // after this state is created, we must conservatively accumulate this flag
+    // from the known render surface instead of checking if the combined
+    // transform is 2d axis aligned, in case of:
+    //  Effect1 (Current known render surface)
+    //  Rotate(45deg)
+    //  Effect2 (Not known now, but may become render surface later)
+    //  Rotate(-45deg)
+    //  Clip (Would be mistakenly treated as 2d axis aligned if we used
+    //
+    // It's lazily computed if it can't be trivially known when we create this
+    // EffectState.
+    enum {
+      kAligned,
+      kMisaligned,
+      kUnknown,
+    } may_be_2d_axis_misaligned_to_render_surface;
+
+    // Whether this effect or an ancestor has a synthetic rounded clip between
+    // self and the next render surface. This is used to force a render surface
+    // for all ancestor synthetic rounded clips if a descendant is found.
+    bool contained_by_non_render_surface_synthetic_rounded_clip;
+
+    // The transform space of the state.
+    const TransformPaintPropertyNode& Transform() const;
+  };
+
   void BuildEffectNodesRecursively(const EffectPaintPropertyNode& next_effect);
+  void ForceRenderSurfaceIfSyntheticRoundedCornerClip(EffectState& state);
   SkBlendMode SynthesizeCcEffectsForClipsIfNeeded(
       const ClipPaintPropertyNode& target_clip,
       SkBlendMode delegated_blend);
@@ -172,23 +236,10 @@
     return current_.effect_type & CcEffectType::kSyntheticForNonTrivialClip;
   }
 
-  // The type of operation the current cc effect node applies.
-  enum CcEffectType {
-    // The cc effect corresponds to a Blink effect node.
-    kEffect = 0,
-    // The cc effect is synthetic for a blink clip node that has to be
-    // rasterized because the clip is non-trivial.
-    kSyntheticForNonTrivialClip = 1 << 0,
-    // The cc effect is synthetic to create a render surface that is
-    // 2d-axis-aligned with a blink clip node that is non-2d-axis-aligned
-    // in the the original render surface. Cc requires a rectangular clip to be
-    // 2d-axis-aligned with the render surface to correctly apply the clip.
-    // TODO(crbug.com/504464): This will be changed when we move render surface
-    // decision logic into the cc compositor thread.
-    kSyntheticFor2dAxisAlignment = 1 << 1
-  };
-
-  CcEffectType SyntheticEffectType(const ClipPaintPropertyNode&) const;
+  bool EffectStateMayBe2dAxisMisalignedToRenderSurface(EffectState&,
+                                                       size_t index);
+  bool CurrentEffectMayBe2dAxisMisalignedToRenderSurface();
+  CcEffectType SyntheticEffectType(const ClipPaintPropertyNode&);
 
   void SetCurrentEffectState(const cc::EffectNode&,
                              CcEffectType,
@@ -221,42 +272,6 @@
 
   int new_sequence_number_ = -1;
 
-  struct EffectState {
-    // The cc effect node that has the corresponding drawing state to the
-    // effect and clip state from the last
-    // SwitchToEffectNodeWithSynthesizedClip.
-    int effect_id;
-
-    CcEffectType effect_type;
-
-    // The effect state of the cc effect node. It's never nullptr.
-    const EffectPaintPropertyNode* effect;
-
-    // The clip state of the cc effect node. This value may be shallower than
-    // the one passed into SwitchToEffectNodeWithSynthesizedClip because not
-    // every clip needs to be synthesized as cc effect. Is set to output clip of
-    // the effect if the type is kEffect, or set to the synthesized clip node.
-    // It's never nullptr.
-    const ClipPaintPropertyNode* clip;
-
-    // Whether the transform space of this state may be 2d axis misaligned to
-    // the containing render surface. As there may be new render surfaces
-    // created between this state and the current known ancestor render surface
-    // after this state is created, we must conservatively accumulate this flag
-    // from the known render surface instead of checking if the combined
-    // transform is 2d axis aligned, in case of:
-    //  Effect1 (Current known render surface)
-    //  Rotate(45deg)
-    //  Effect2 (Not known now, but may become render surface later)
-    //  Rotate(-45deg)
-    //  Clip (Would be mistakenly treated as 2d axis aligned if we used
-    //        accumulated transform from the clip to the known render surface.)
-    bool may_be_2d_axis_misaligned_to_render_surface;
-
-    // The transform space of the state.
-    const TransformPaintPropertyNode& Transform() const;
-  };
-
   // The current effect state. Virtually it's the top of the effect stack if
   // it and effect_stack_ are treated as a whole stack.
   EffectState current_;
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
index 5bb0bd9..330047d7 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -34,6 +34,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/numerics/checked_math.h"
 #include "build/build_config.h"
 #include "cc/layers/texture_layer.h"
@@ -291,9 +292,8 @@
   viz::ResourceFormat format = viz::RGBA_8888;
   if (use_half_float_storage_)
     format = viz::RGBA_F16;
-  std::unique_ptr<base::SharedMemory> shm =
-      viz::bitmap_allocation::AllocateMappedBitmap(
-          static_cast<gfx::Size>(size_), format);
+  base::MappedReadOnlyRegion shm = viz::bitmap_allocation::AllocateSharedBitmap(
+      static_cast<gfx::Size>(size_), format);
   auto bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
       id, std::move(shm), static_cast<gfx::Size>(size_), format);
   RegisteredBitmap registered = {
@@ -358,8 +358,8 @@
 
   // Read the framebuffer into |bitmap|.
   {
-    unsigned char* pixels = static_cast<unsigned char*>(
-        registered.bitmap->shared_memory()->memory());
+    unsigned char* pixels =
+        static_cast<unsigned char*>(registered.bitmap->memory());
     DCHECK(pixels);
     bool need_premultiply = want_alpha_channel_ && !premultiplied_alpha_;
     WebGLImageConversion::AlphaOp op =
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
index acf73ec..48aa23b0 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h"
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "cc/layers/texture_layer.h"
 #include "cc/resources/cross_thread_shared_bitmap.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
@@ -150,7 +151,7 @@
     SkImageInfo dst_info =
         SkImageInfo::Make(size.width(), size.height(), sk_image->colorType(),
                           kPremul_SkAlphaType, sk_image->refColorSpace());
-    void* pixels = registered.bitmap->shared_memory()->memory();
+    void* pixels = registered.bitmap->memory();
 
     // Copy from SkImage into SharedMemory owned by |registered|.
     if (!sk_image->readPixels(dst_info, pixels, dst_info.minRowBytes(), 0, 0))
@@ -195,8 +196,8 @@
 
   // There are no bitmaps to recycle so allocate a new one.
   viz::SharedBitmapId id = viz::SharedBitmap::GenerateId();
-  std::unique_ptr<base::SharedMemory> shm =
-      viz::bitmap_allocation::AllocateMappedBitmap(size, format);
+  base::MappedReadOnlyRegion shm =
+      viz::bitmap_allocation::AllocateSharedBitmap(size, format);
 
   RegisteredBitmap registered;
   registered.bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>(
diff --git a/third_party/blink/renderer/platform/heap/gc_info.h b/third_party/blink/renderer/platform/heap/gc_info.h
index f9934e8..2109246a 100644
--- a/third_party/blink/renderer/platform/heap/gc_info.h
+++ b/third_party/blink/renderer/platform/heap/gc_info.h
@@ -30,7 +30,6 @@
   const TraceCallback trace;
   const FinalizationCallback finalize;
   const NameCallback name;
-  const bool non_trivial_finalizer;
   const bool has_v_table;
 };
 
@@ -102,9 +101,10 @@
   static uint32_t Index() {
     static_assert(sizeof(T), "T must be fully defined");
     static const GCInfo kGcInfo = {
-        TraceTrait<T>::Trace, FinalizerTrait<T>::Finalize,
-        NameTrait<T>::GetName, FinalizerTrait<T>::kNonTrivialFinalizer,
-        std::is_polymorphic<T>::value};
+        TraceTrait<T>::Trace,
+        FinalizerTrait<T>::kNonTrivialFinalizer ? FinalizerTrait<T>::Finalize
+                                                : nullptr,
+        NameTrait<T>::GetName, std::is_polymorphic<T>::value};
     // This is more complicated than using threadsafe initialization, but this
     // is instantiated many times (once for every GC type).
     static std::atomic_uint32_t gc_info_index{0};
diff --git a/third_party/blink/renderer/platform/heap/gc_info_test.cc b/third_party/blink/renderer/platform/heap/gc_info_test.cc
index e87246b..a263ac1 100644
--- a/third_party/blink/renderer/platform/heap/gc_info_test.cc
+++ b/third_party/blink/renderer/platform/heap/gc_info_test.cc
@@ -15,7 +15,7 @@
 
 TEST(GCInfoTest, ResizeToMaxIndex) {
   GCInfoTable table;
-  GCInfo info = {nullptr, nullptr, nullptr, false, false};
+  GCInfo info = {nullptr, nullptr, nullptr, false};
   std::atomic_uint32_t slot{0};
   for (uint32_t i = 0; i < (GCInfoTable::kMaxIndex - 1); i++) {
     slot = 0;
diff --git a/third_party/blink/renderer/platform/heap/heap.h b/third_party/blink/renderer/platform/heap/heap.h
index 0741595..508e2f9 100644
--- a/third_party/blink/renderer/platform/heap/heap.h
+++ b/third_party/blink/renderer/platform/heap/heap.h
@@ -697,7 +697,7 @@
   // TODO(haraken): We don't support reallocate() for finalizable objects.
   DCHECK(!GCInfoTable::Get()
               .GCInfoFromIndex(previous_header->GcInfoIndex())
-              ->non_trivial_finalizer);
+              ->finalize);
   DCHECK_EQ(previous_header->GcInfoIndex(), gc_info_index);
   HeapAllocHooks::FreeHookIfEnabled(static_cast<Address>(previous));
   Address address;
diff --git a/third_party/blink/renderer/platform/heap/heap_page.cc b/third_party/blink/renderer/platform/heap/heap_page.cc
index d120b441..9d02a88 100644
--- a/third_party/blink/renderer/platform/heap/heap_page.cc
+++ b/third_party/blink/renderer/platform/heap/heap_page.cc
@@ -99,7 +99,7 @@
 void HeapObjectHeader::Finalize(Address object, size_t object_size) {
   HeapAllocHooks::FreeHookIfEnabled(object);
   const GCInfo* gc_info = GCInfoTable::Get().GCInfoFromIndex(GcInfoIndex());
-  if (gc_info->non_trivial_finalizer)
+  if (gc_info->finalize)
     gc_info->finalize(object);
 
   ASAN_RETIRE_CONTAINER_ANNOTATION(object, object_size);
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 652e330..f530ed60 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5571,7 +5571,6 @@
 crbug.com/874162 [ Mac ] virtual/user-activation-v2/fast/events/selection-autoscroll-borderbelt.html [ Skip ]
 
 # Flaky
-crbug.com/957916 fullscreen/rendering/backdrop-object.html [ Timeout Pass ]
 crbug.com/894077 virtual/android/fullscreen/video-fixed-background.html [ Pass Failure ]
 
 # Failures on 32-bit Android when using GPU instead of software rendering
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked.html
similarity index 76%
rename from third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked.html
index 6efded8..b8443767 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked.html
@@ -6,6 +6,9 @@
         <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script type="text/javascript" src="../pointerevent_support.js"></script>
         <style>
           #testContainer {
@@ -18,6 +21,7 @@
             var lock_change_count = 0;
             var mouseeventMovements = []
             var pointereventMovements = []
+            var has_coalesced_Events = false;
 
             function resetTestState() {
             }
@@ -34,10 +38,18 @@
                         test_pointerEvent.step(function() {
                             assert_greater_than(event.getCoalescedEvents().length, 0, "document.pointerLockElement should have coalesced events.");
                             document.exitPointerLock();
-                            test_pointerEvent.done();
+                            has_coalesced_Events = true;;
                         });
                     }
                 });
+
+                // Inject mouse inputs.
+                pointerDragInTarget('mouse', div1, 'right').then(function() {
+                    test_pointerEvent.step(function () {
+                        assert_true(has_coalesced_Events, "document.pointerLockElement should have coalesced events.");
+                    }, "document.pointerLockElement should have coalesced events.");
+                    test_pointerEvent.done();
+                });
             }
         </script>
     </head>
@@ -59,4 +71,4 @@
         </div>
         <div class="spacer"></div>
     </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing.html
similarity index 91%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing.html
index 0de4d55..1ed26eb 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_boundary_events_in_capturing.html
@@ -6,6 +6,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <!-- Additional helper script for common checks across event types -->
         <script type="text/javascript" src="pointerevent_support.js"></script>
         <script>
@@ -67,6 +70,13 @@
                         }
                     });
                 });
+
+                // Inject pointer inputs.
+                pointerDragInTarget('mouse', target0, 'right').then(function() {
+                    return pointerDragInTarget('touch', target0, 'right');
+                }).then(function() {
+                    return pointerDragInTarget('pen', target0, 'right');
+                });
             }
         </script>
     </head>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target.html
index 89f3d839..12e31cdb 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target.html
@@ -8,6 +8,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <!-- Additional helper script for common checks across event types -->
         <script type="text/javascript" src="pointerevent_support.js"></script>
         <script type="text/javascript">
@@ -111,6 +114,13 @@
                     on_event(target0, All_Pointer_Events[i], targetEventHandler);
                     on_event(listener, All_Pointer_Events[i], listenerEventHandler);
                 }
+
+                // Inject pointer inputs.
+                pointerDragInTarget('mouse', target0, 'right').then(function() {
+                    return pointerDragInTarget('touch', target0, 'right');
+                }).then(function() {
+                    return pointerDragInTarget('pen', target0, 'right');
+                });
             }
         </script>
     </head>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag.html
similarity index 80%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag.html
index 982167d..88d5eed 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_sequence_at_implicit_release_on_drag.html
@@ -8,6 +8,9 @@
     <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
     <script type="text/javascript" src="pointerevent_support.js"></script>
     <script type="text/javascript">
       var detected_pointertypes = {};
@@ -23,12 +26,14 @@
       function run() {
         var test_pointer_event = setup_pointerevent_test("Event sequence at implicit release on drag", ["touch"]);
 
+        var button = document.getElementById("done");
+        var clickIsReceived = false;
         on_event(document.getElementById("done"), "click", function() {
           test_pointer_event.step(function () {
             var expected_events = "pointercancel, lostpointercapture, pointerout, pointerleave";
             assert_equals(event_log.join(", "), expected_events);
           });
-          test_pointer_event.done();
+          clickIsReceived = true;
         });
 
         var target = document.getElementById("target");
@@ -48,6 +53,16 @@
             }
           });
         });
+
+        // Inject touch inputs.
+        pointerDragInTarget("touch", target, 'right').then(function() {
+          return touchTapInTarget(button);
+        }).then(function() {
+          test_pointer_event.step(function () {
+            assert_true(clickIsReceived, "click should be received before the test finishes");
+          }, "click should be received before the test finishes");
+          test_pointer_event.done();
+        });
       }
     </script>
     <style>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked.html
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked.html
index ccb8c27..bdad97df 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerlock/pointerevent_movementxy_when_locked.html
@@ -6,6 +6,9 @@
         <link rel="stylesheet" type="text/css" href="/external/wpt/pointerevents/pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script type="text/javascript" src="../pointerevent_support.js"></script>
         <style>
           #testContainer {
@@ -59,6 +62,9 @@
                         test_pointerEvent.done();
                     }
                 });
+
+                // Inject mouse inputs.
+                pointerDragInTarget('mouse', target, 'right');
             }
         </script>
     </head>
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual-automation.js
deleted file mode 100644
index 6d50711..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/extension/pointerevent_getCoalescedEvents_when_pointerlocked-manual-automation.js
+++ /dev/null
@@ -1,5 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return pointerDragInTarget('mouse', '#target', 'left');
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_boundary_events_in_capturing-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_boundary_events_in_capturing-manual-automation.js
deleted file mode 100644
index 6cd7425..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_boundary_events_in_capturing-manual-automation.js
+++ /dev/null
@@ -1,9 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return pointerDragInTarget('mouse', '#target0', 'right').then(function() {
-    return pointerDragInTarget('touch', '#target0', 'right');
-  }).then(function() {
-    return pointerDragInTarget('pen', '#target0', 'right');
-  });
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual-automation.js
deleted file mode 100644
index 6cd7425..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual-automation.js
+++ /dev/null
@@ -1,9 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return pointerDragInTarget('mouse', '#target0', 'right').then(function() {
-    return pointerDragInTarget('touch', '#target0', 'right');
-  }).then(function() {
-    return pointerDragInTarget('pen', '#target0', 'right');
-  });
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual-automation.js
deleted file mode 100644
index c0b9fbf..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_sequence_at_implicit_release_on_drag-manual-automation.js
+++ /dev/null
@@ -1,7 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return pointerDragInTarget('touch', '#target', 'right').then(function() {
-    return touchTapInTarget('#done');
-  });
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual-automation.js
deleted file mode 100644
index 684858a..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerlock/pointerevent_movementxy_when_locked-manual-automation.js
+++ /dev/null
@@ -1,5 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return pointerDragInTarget('mouse', '#target', 'right');
-}
diff --git a/third_party/blink/web_tests/fullscreen/rendering/backdrop-object.html b/third_party/blink/web_tests/fullscreen/rendering/backdrop-object.html
index 156cbec1..fff65b74 100644
--- a/third_party/blink/web_tests/fullscreen/rendering/backdrop-object.html
+++ b/third_party/blink/web_tests/fullscreen/rendering/backdrop-object.html
@@ -8,12 +8,13 @@
 </style>
 <object width="200" type="image/svg+xml" data="../../svg/as-image/resources/circle.svg"></object>
 <script>
+var object = document.querySelector("object");
+Promise.all([
+    new Promise((resolve, reject) => document.addEventListener("fullscreenchange", resolve)),
+    new Promise((resolve, reject) => object.addEventListener("load", resolve))
+]).then(() => testRunner.notifyDone());
+
 testRunner.waitUntilDone();
 var t = { step_func: func => func() };
-trusted_request(t, document.querySelector("object"));
-document.addEventListener("fullscreenchange", function() {
-  document.querySelector("object").addEventListener("load", () => {
-    testRunner.notifyDone();
-  });
-});
+trusted_request(t, object);
 </script>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
index f099b8be..fe83083 100644
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
@@ -55,9 +55,8 @@
 }, "Hit-test properties are not available with a WebXR token.");
 test(t => {
   let hittest_interfaces = [
-    'XRHitResult'/*,
-    TODO(https://crbug.com/958561): Uncomment after resolving the issue.
-    'XRRay'*/
+    'XRHitResult',
+    'XRRay'
   ];
   OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag(
     this, hittest_interfaces, 'webXRHitTestEnabled');
diff --git a/third_party/blink/web_tests/plugins/object-change-attribute-reattach.html b/third_party/blink/web_tests/plugins/object-change-attribute-reattach.html
new file mode 100644
index 0000000..46c6568
--- /dev/null
+++ b/third_party/blink/web_tests/plugins/object-change-attribute-reattach.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<object id="target"></object>
+<script>
+  // Trigger layout now to create LayoutEmbeddedObject
+  document.body.offsetTop;
+
+  // Changing it to an image should trigger a re-attach as LayoutImage. This is
+  // the bugfix. Previously this didn't happen.
+  target.type = "image/gif";
+
+  // Change some style that would trigger re-layout (also without the fix), and
+  // creation of a Frame object.
+  target.style.width = "100px";
+  document.body.offsetTop;
+
+  // Without the bugfix, the OBJECT would still be LayoutEmbeddedObject at this
+  // point, but changing the display type, would trigger reattachment, and
+  // *then* it would become a LayoutImage (and it would attempt to re-use
+  // the Frame, because nobody told it not to).
+  target.style.display = "block";
+  document.body.offsetTop;
+
+  test(()=> { }, "No crash or DCHECK failure");
+</script>
diff --git a/third_party/sqlite/amalgamation/sqlite3.c b/third_party/sqlite/amalgamation/sqlite3.c
index b84901b..5aaf9ddd 100644
--- a/third_party/sqlite/amalgamation/sqlite3.c
+++ b/third_party/sqlite/amalgamation/sqlite3.c
@@ -74663,13 +74663,19 @@
 /*
 ** It is already known that pMem contains an unterminated string.
 ** Add the zero terminator.
+**
+** Three bytes of zero are added.  In this way, there is guaranteed
+** to be a double-zero byte at an even byte boundary in order to
+** terminate a UTF16 string, even if the initial size of the buffer
+** is an odd number of bytes.
 */
 static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){
-  if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){
+  if( sqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){
     return SQLITE_NOMEM_BKPT;
   }
   pMem->z[pMem->n] = 0;
   pMem->z[pMem->n+1] = 0;
+  pMem->z[pMem->n+2] = 0;
   pMem->flags |= MEM_Term;
   return SQLITE_OK;
 }
@@ -74743,9 +74749,9 @@
 }
 
 /*
-** Add MEM_Str to the set of representations for the given Mem.  Numbers
-** are converted using sqlite3_snprintf().  Converting a BLOB to a string
-** is a no-op.
+** Add MEM_Str to the set of representations for the given Mem.  This
+** routine is only called if pMem is a number of some kind, not a NULL
+** or a BLOB.
 **
 ** Existing representations MEM_Int and MEM_Real are invalidated if
 ** bForce is true but are retained if bForce is false.
@@ -83734,7 +83740,7 @@
     ** is clear. Otherwise, if this is an ephemeral cursor created by
     ** OP_OpenDup, the cursor will not be closed and will still be part
     ** of a BtShared.pCursor list.  */
-    p->apCsr[iCur]->isEphemeral = 0;
+    if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
     sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
     p->apCsr[iCur] = 0;
   }
@@ -87245,7 +87251,10 @@
   if( pCx ){
     /* If the ephermeral table is already open, erase all existing content
     ** so that the table is empty again, rather than creating a new table. */
-    rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
+    assert( pCx->isEphemeral );
+    if( pCx->pBtx ){
+      rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
+    }
   }else{
     pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
     if( pCx==0 ) goto no_mem;
@@ -222368,7 +222377,7 @@
 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
 
 /************** End of stmt.c ************************************************/
-#if __LINE__!=222371
+#if __LINE__!=222380
 #undef SQLITE_SOURCE_ID
 #define SQLITE_SOURCE_ID      "2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f8315alt2"
 #endif
diff --git a/third_party/sqlite/patched/src/vdbe.c b/third_party/sqlite/patched/src/vdbe.c
index 2acc5873..b563627 100644
--- a/third_party/sqlite/patched/src/vdbe.c
+++ b/third_party/sqlite/patched/src/vdbe.c
@@ -264,7 +264,7 @@
     ** is clear. Otherwise, if this is an ephemeral cursor created by
     ** OP_OpenDup, the cursor will not be closed and will still be part
     ** of a BtShared.pCursor list.  */
-    p->apCsr[iCur]->isEphemeral = 0;
+    if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
     sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
     p->apCsr[iCur] = 0;
   }
@@ -3686,7 +3686,10 @@
   if( pCx ){
     /* If the ephermeral table is already open, erase all existing content
     ** so that the table is empty again, rather than creating a new table. */
-    rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
+    assert( pCx->isEphemeral );
+    if( pCx->pBtx ){
+      rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
+    }
   }else{
     pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
     if( pCx==0 ) goto no_mem;
diff --git a/third_party/sqlite/patched/src/vdbemem.c b/third_party/sqlite/patched/src/vdbemem.c
index 8914aa2..fbcec0f5 100644
--- a/third_party/sqlite/patched/src/vdbemem.c
+++ b/third_party/sqlite/patched/src/vdbemem.c
@@ -255,13 +255,19 @@
 /*
 ** It is already known that pMem contains an unterminated string.
 ** Add the zero terminator.
+**
+** Three bytes of zero are added.  In this way, there is guaranteed
+** to be a double-zero byte at an even byte boundary in order to
+** terminate a UTF16 string, even if the initial size of the buffer
+** is an odd number of bytes.
 */
 static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){
-  if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){
+  if( sqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){
     return SQLITE_NOMEM_BKPT;
   }
   pMem->z[pMem->n] = 0;
   pMem->z[pMem->n+1] = 0;
+  pMem->z[pMem->n+2] = 0;
   pMem->flags |= MEM_Term;
   return SQLITE_OK;
 }
@@ -335,9 +341,9 @@
 }
 
 /*
-** Add MEM_Str to the set of representations for the given Mem.  Numbers
-** are converted using sqlite3_snprintf().  Converting a BLOB to a string
-** is a no-op.
+** Add MEM_Str to the set of representations for the given Mem.  This
+** routine is only called if pMem is a number of some kind, not a NULL
+** or a BLOB.
 **
 ** Existing representations MEM_Int and MEM_Real are invalidated if
 ** bForce is true but are retained if bForce is false.
diff --git a/third_party/sqlite/patched/test/with3.test b/third_party/sqlite/patched/test/with3.test
index 0fd21aa..f651207d2 100644
--- a/third_party/sqlite/patched/test/with3.test
+++ b/third_party/sqlite/patched/test/with3.test
@@ -130,4 +130,40 @@
   `--SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)
 }
 
+do_execsql_test 4.0 {
+  WITH t5(t5col1) AS (
+    SELECT (
+      WITH t3(t3col1) AS (
+        WITH t2 AS (
+          WITH t1 AS (SELECT 1 AS c1 GROUP BY 1)
+          SELECT a.c1 FROM t1 AS a, t1 AS b
+          WHERE anoncol1 = 1
+        )
+        SELECT (SELECT 1 FROM t2) FROM t2
+      )
+      SELECT t3col1 FROM t3 WHERE t3col1
+    ) FROM (SELECT 1 AS anoncol1)
+  )
+  SELECT t5col1, t5col1 FROM t5
+} {1 1}
+do_execsql_test 4.1 {
+  SELECT EXISTS (
+    WITH RECURSIVE Table0 AS (
+      WITH RECURSIVE Table0(Col0) AS (SELECT ALL 1  )
+      SELECT ALL (
+        WITH RECURSIVE Table0 AS (
+          WITH RECURSIVE Table0 AS (
+            WITH RECURSIVE Table0 AS (SELECT DISTINCT 1  GROUP BY 1  )
+            SELECT DISTINCT * FROM Table0 NATURAL INNER JOIN Table0
+            WHERE Col0 = 1
+          )
+          SELECT ALL (SELECT DISTINCT * FROM Table0) FROM Table0 WHERE Col0 = 1
+        )
+        SELECT ALL * FROM Table0  NATURAL INNER JOIN  Table0
+      ) FROM Table0 )
+      SELECT DISTINCT * FROM Table0  NATURAL INNER JOIN  Table0
+    );
+} {1}
+
+
 finish_test
diff --git a/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch b/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
index 74daf68b..1bf4742 100644
--- a/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
+++ b/third_party/sqlite/patches/0001-Virtual-table-supporting-recovery-of-corrupted-datab.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Scott Hess <shess@chromium.org>
 Date: Sat, 20 Jul 2013 11:42:21 -0700
-Subject: [PATCH 1/4] Virtual table supporting recovery of corrupted databases.
+Subject: [PATCH 1/6] Virtual table supporting recovery of corrupted databases.
 
 "recover" implements a virtual table which uses the SQLite pager layer
 to read table pages and pull out the data which is structurally sound
diff --git a/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch b/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
index 5ee7e840..6a2708d 100644
--- a/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
+++ b/third_party/sqlite/patches/0002-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: "tc@google.com" <tc@google.com>
 Date: Tue, 6 Jan 2009 22:39:41 +0000
-Subject: [PATCH 2/4] Custom shell.c helpers to load Chromium's ICU data.
+Subject: [PATCH 2/6] Custom shell.c helpers to load Chromium's ICU data.
 
 History uses fts3 with an icu-based segmenter.  These changes allow building a
 sqlite3 binary for Linux or Windows which can read those files.
diff --git a/third_party/sqlite/patches/0003-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch b/third_party/sqlite/patches/0003-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
index dd4d18ea..03e8e7a3 100644
--- a/third_party/sqlite/patches/0003-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
+++ b/third_party/sqlite/patches/0003-Don-t-generate-VDBE-code-for-VACUUM-after-a-syntax-e.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 1 May 2019 14:44:32 -0700
-Subject: [PATCH 3/4] Don't generate VDBE code for VACUUM after a syntax error
+Subject: [PATCH 3/6] Don't generate VDBE code for VACUUM after a syntax error
 
 This backports https://www.sqlite.org/src/info/930842470da27d72
 
diff --git a/third_party/sqlite/patches/0004-Fix-use-after-free-xDestroy-error.patch b/third_party/sqlite/patches/0004-Fix-use-after-free-xDestroy-error.patch
index 83387ed..26dd9df 100644
--- a/third_party/sqlite/patches/0004-Fix-use-after-free-xDestroy-error.patch
+++ b/third_party/sqlite/patches/0004-Fix-use-after-free-xDestroy-error.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Darwin Huang <huangdarwin@chromium.org>
 Date: Wed, 1 May 2019 14:47:06 -0700
-Subject: [PATCH 4/4] Fix use-after-free xDestroy error
+Subject: [PATCH 4/6] Fix use-after-free xDestroy error
 
 This backports https://www.sqlite.org/src/info/1dbbb0101e8213b9
 
diff --git a/third_party/sqlite/patches/0005-Fix-memory-leak-segfault.patch b/third_party/sqlite/patches/0005-Fix-memory-leak-segfault.patch
new file mode 100644
index 0000000..0f91f26
--- /dev/null
+++ b/third_party/sqlite/patches/0005-Fix-memory-leak-segfault.patch
@@ -0,0 +1,86 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Darwin Huang <huangdarwin@chromium.org>
+Date: Fri, 3 May 2019 14:46:06 -0700
+Subject: [PATCH 5/6] Fix memory-leak/segfault
+
+Backports https://www.sqlite.org/src/info/a9b90aa12eecdd9f
+
+Bug: 958351
+---
+ third_party/sqlite/patched/src/vdbe.c      |  7 +++--
+ third_party/sqlite/patched/test/with3.test | 36 ++++++++++++++++++++++
+ 2 files changed, 41 insertions(+), 2 deletions(-)
+
+diff --git a/third_party/sqlite/patched/src/vdbe.c b/third_party/sqlite/patched/src/vdbe.c
+index 2acc5873c531..b563627af83a 100644
+--- a/third_party/sqlite/patched/src/vdbe.c
++++ b/third_party/sqlite/patched/src/vdbe.c
+@@ -264,7 +264,7 @@ static VdbeCursor *allocateCursor(
+     ** is clear. Otherwise, if this is an ephemeral cursor created by
+     ** OP_OpenDup, the cursor will not be closed and will still be part
+     ** of a BtShared.pCursor list.  */
+-    p->apCsr[iCur]->isEphemeral = 0;
++    if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
+     sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
+     p->apCsr[iCur] = 0;
+   }
+@@ -3686,7 +3686,10 @@ case OP_OpenEphemeral: {
+   if( pCx ){
+     /* If the ephermeral table is already open, erase all existing content
+     ** so that the table is empty again, rather than creating a new table. */
+-    rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
++    assert( pCx->isEphemeral );
++    if( pCx->pBtx ){
++      rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
++    }
+   }else{
+     pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
+     if( pCx==0 ) goto no_mem;
+diff --git a/third_party/sqlite/patched/test/with3.test b/third_party/sqlite/patched/test/with3.test
+index 0fd21aa92e48..f651207d2901 100644
+--- a/third_party/sqlite/patched/test/with3.test
++++ b/third_party/sqlite/patched/test/with3.test
+@@ -130,4 +130,40 @@ do_eqp_test 3.2.2 {
+   `--SEARCH TABLE w1 USING INTEGER PRIMARY KEY (rowid=?)
+ }
+ 
++do_execsql_test 4.0 {
++  WITH t5(t5col1) AS (
++    SELECT (
++      WITH t3(t3col1) AS (
++        WITH t2 AS (
++          WITH t1 AS (SELECT 1 AS c1 GROUP BY 1)
++          SELECT a.c1 FROM t1 AS a, t1 AS b
++          WHERE anoncol1 = 1
++        )
++        SELECT (SELECT 1 FROM t2) FROM t2
++      )
++      SELECT t3col1 FROM t3 WHERE t3col1
++    ) FROM (SELECT 1 AS anoncol1)
++  )
++  SELECT t5col1, t5col1 FROM t5
++} {1 1}
++do_execsql_test 4.1 {
++  SELECT EXISTS (
++    WITH RECURSIVE Table0 AS (
++      WITH RECURSIVE Table0(Col0) AS (SELECT ALL 1  )
++      SELECT ALL (
++        WITH RECURSIVE Table0 AS (
++          WITH RECURSIVE Table0 AS (
++            WITH RECURSIVE Table0 AS (SELECT DISTINCT 1  GROUP BY 1  )
++            SELECT DISTINCT * FROM Table0 NATURAL INNER JOIN Table0
++            WHERE Col0 = 1
++          )
++          SELECT ALL (SELECT DISTINCT * FROM Table0) FROM Table0 WHERE Col0 = 1
++        )
++        SELECT ALL * FROM Table0  NATURAL INNER JOIN  Table0
++      ) FROM Table0 )
++      SELECT DISTINCT * FROM Table0  NATURAL INNER JOIN  Table0
++    );
++} {1}
++
++
+ finish_test
+-- 
+2.21.0.1020.gf2820cf01a-goog
+
diff --git a/third_party/sqlite/patches/0006-Ensure-UTF16-strings-are-zero-terminated.patch b/third_party/sqlite/patches/0006-Ensure-UTF16-strings-are-zero-terminated.patch
new file mode 100644
index 0000000..3368e3d
--- /dev/null
+++ b/third_party/sqlite/patches/0006-Ensure-UTF16-strings-are-zero-terminated.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Darwin Huang <huangdarwin@chromium.org>
+Date: Fri, 3 May 2019 14:49:17 -0700
+Subject: [PATCH 6/6] Ensure UTF16 strings are zero-terminated
+
+Backports https://www.sqlite.org/src/info/3a16ddf91f0c9c51
+
+Bug: 959193
+---
+ third_party/sqlite/patched/src/vdbemem.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/third_party/sqlite/patched/src/vdbemem.c b/third_party/sqlite/patched/src/vdbemem.c
+index 8914aa269758..fbcec0f59c18 100644
+--- a/third_party/sqlite/patched/src/vdbemem.c
++++ b/third_party/sqlite/patched/src/vdbemem.c
+@@ -255,13 +255,19 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){
+ /*
+ ** It is already known that pMem contains an unterminated string.
+ ** Add the zero terminator.
++**
++** Three bytes of zero are added.  In this way, there is guaranteed
++** to be a double-zero byte at an even byte boundary in order to
++** terminate a UTF16 string, even if the initial size of the buffer
++** is an odd number of bytes.
+ */
+ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){
+-  if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){
++  if( sqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){
+     return SQLITE_NOMEM_BKPT;
+   }
+   pMem->z[pMem->n] = 0;
+   pMem->z[pMem->n+1] = 0;
++  pMem->z[pMem->n+2] = 0;
+   pMem->flags |= MEM_Term;
+   return SQLITE_OK;
+ }
+@@ -335,9 +341,9 @@ int sqlite3VdbeMemNulTerminate(Mem *pMem){
+ }
+ 
+ /*
+-** Add MEM_Str to the set of representations for the given Mem.  Numbers
+-** are converted using sqlite3_snprintf().  Converting a BLOB to a string
+-** is a no-op.
++** Add MEM_Str to the set of representations for the given Mem.  This
++** routine is only called if pMem is a number of some kind, not a NULL
++** or a BLOB.
+ **
+ ** Existing representations MEM_Int and MEM_Real are invalidated if
+ ** bForce is true but are retained if bForce is false.
+-- 
+2.21.0.1020.gf2820cf01a-goog
+
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index bd2c455..93eabbe 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -5885,6 +5885,11 @@
   <int value="1" label="Needs to be duplications cleared"/>
 </enum>
 
+<enum name="BooleanNeedsNormalization">
+  <int value="0" label="Doesn't need normalization"/>
+  <int value="1" label="Needs normalization"/>
+</enum>
+
 <enum name="BooleanNetworkQuietBeforeSwap">
   <int value="0" label="FMP OK, network not yet quiet"/>
   <int value="1" label="FMP not OK, network was already quiet"/>
@@ -32761,6 +32766,8 @@
   <int value="-1677715989" label="UnifiedConsent:disabled"/>
   <int value="-1676256979"
       label="AutofillUpstreamUseGooglePayOnAndroidBranding:enabled"/>
+  <int value="-1672160462"
+      label="SwapSideVolumeButtonsForOrientation:disabled"/>
   <int value="-1670137340"
       label="OptimizeLoadingIPCForSmallResources:disabled"/>
   <int value="-1669486359" label="ImportantSitesInCBD:enabled"/>
@@ -32895,6 +32902,7 @@
   <int value="-1473878093" label="HideArcMediaNotifications:disabled"/>
   <int value="-1473668019" label="token-binding:disabled"/>
   <int value="-1473136627" label="enable-web-payments"/>
+  <int value="-1472825316" label="ContextualSearchLongpressResolve:enabled"/>
   <int value="-1471021059"
       label="OmniboxUIExperimentShowSuggestionFavicons:disabled"/>
   <int value="-1469536698" label="ChromeHomeDoodle:enabled"/>
@@ -33588,6 +33596,7 @@
   <int value="-456321929" label="ForceEnableSystemAec:disabled"/>
   <int value="-455203267" label="use_new_features_summary"/>
   <int value="-449465495" label="disable-browser-task-scheduler"/>
+  <int value="-438379844" label="SwapSideVolumeButtonsForOrientation:enabled"/>
   <int value="-436470115" label="TouchpadAndWheelScrollLatching:enabled"/>
   <int value="-435914745" label="ClipboardContentSetting:disabled"/>
   <int value="-430360431" label="disable-password-generation"/>
@@ -33944,6 +33953,7 @@
   <int value="92327255" label="DisplayMoveWindowAccels:disabled"/>
   <int value="93832899" label="NtpCustomizationMenuV2:enabled"/>
   <int value="98134240" label="material-design-ink-drop-animation-speed"/>
+  <int value="98218116" label="ContextualSearchLongpressResolve:disabled"/>
   <int value="99177659" label="OmniboxUICuesForSearchHistoryMatches:disabled"/>
   <int value="103932290" label="show-autofill-type-predictions"/>
   <int value="105046382" label="ParallelDownloading:disabled"/>
@@ -38768,6 +38778,13 @@
   <int value="3" label="NO_ENTRY"/>
 </enum>
 
+<enum name="NetCertificateNameNormalization">
+  <int value="0" label="Error parsing certificate chain"/>
+  <int value="1" label="Byte equal Names"/>
+  <int value="2" label="Normalized Names"/>
+  <int value="3" label="Chain contained only one certificate"/>
+</enum>
+
 <enum name="NetConnectivityProtocolStatus">
   <int value="0" label="SUCCESS"/>
   <int value="1" label="IP_STRING_PARSE_FAILED"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 5d20341a..0b8cd3c 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -63931,6 +63931,16 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="Net.CertVerifier.NameNormalizationPrivateRoots"
+    enum="NetCertificateNameNormalization" expires_after="2020-06-01">
+  <owner>mattm@chromium.org</owner>
+  <summary>
+    Whether Name normalization was used in successfully validated certificate
+    chains, only recorded for chains ending in privately trusted roots. Suffixed
+    by the CertVerifyProc implementation.
+  </summary>
+</histogram>
+
 <histogram name="Net.CertVerifier_First_Job_Latency" units="ms"
     expires_after="M77">
   <owner>davidben@chromium.org</owner>
@@ -64400,6 +64410,17 @@
   </summary>
 </histogram>
 
+<histogram name="Net.Cors.ActiveLoaderCount" units="entries"
+    expires_after="M77">
+  <owner>toyoshim@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <summary>
+    The distribution of active mojom::URLLoader instance counts including
+    network::URLLoader and network::CorsURLLoader in the network service. This
+    reports every 5 minutes.
+  </summary>
+</histogram>
+
 <histogram name="Net.Cors.CompletionStatus" enum="CorsCompletionStatus"
     expires_after="M77">
   <owner>toyoshim@chromium.org</owner>
@@ -64416,7 +64437,7 @@
   <owner>yhirano@chromium.org</owner>
   <summary>
     The distribution of the number of cache entries in the CORS preflight cache.
-    This counts the cache entries every hour.
+    This counts the cache entries every 5 minutes.
   </summary>
 </histogram>
 
@@ -64430,6 +64451,17 @@
   </summary>
 </histogram>
 
+<histogram name="Net.Cors.PreflightCacheTotalEntries" units="entries"
+    expires_after="M77">
+  <owner>toyoshim@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <summary>
+    The distribution of the total number of cache entries that is sum of all
+    CORS preflight cache entries in the network service. This counts the cache
+    entries every 5 minutes.
+  </summary>
+</histogram>
+
 <histogram name="Net.CountOfAlternateProtocolServers" units="servers"
     expires_after="M77">
   <owner>bnc@chromium.org</owner>
@@ -87727,6 +87759,16 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.BlacklistedSites.NeedNormalization"
+    enum="BooleanNeedsNormalization" expires_after="M80">
+  <owner>jdoerrie@chromium.org</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    Records once on startup whether the blacklisted sites in the password store
+    need to be normalized.
+  </summary>
+</histogram>
+
 <histogram
     name="PasswordManager.BlacklistedSites.NeedRemoveBlacklistDuplicates"
     enum="BooleanNeedsDeDuplication" expires_after="M73">
@@ -143611,6 +143653,10 @@
 <histogram_suffixes name="AppListTabletModeTransition" separator=".">
   <suffix name="DragReleaseHide" label="Release drag to hide the app list"/>
   <suffix name="DragReleaseShow" label="Release drag to show the app list"/>
+  <suffix name="EnterFullscreenAllApps"
+      label="Enter kFullScreenAllApps state in tablet"/>
+  <suffix name="EnterFullscreenSearch"
+      label="Enter kFullscreenSearch state in tablet"/>
   <suffix name="EnterOverview" label="Enter overview mode in tablet"/>
   <suffix name="ExitOverview" label="Exit overview mode in tablet"/>
   <suffix name="HideLauncherForWindow"
@@ -144688,6 +144734,16 @@
   <affected-histogram name="DiskBasedCertCache.CertIo"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="CertVerifyProcImpl" separator=".">
+  <suffix name="Android" label="CertVerifyProcAndroid"/>
+  <suffix name="Builtin" label="CertVerifyProcBuiltin"/>
+  <suffix name="IOS" label="CertVerifyProcIOS"/>
+  <suffix name="Mac" label="CertVerifyProcMac"/>
+  <suffix name="NSS" label="CertVerifyProcNSS"/>
+  <suffix name="Win" label="CertVerifyProcWin"/>
+  <affected-histogram name="Net.CertVerifier.NameNormalizationPrivateRoots"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="ChromeActivityName" separator=".">
   <suffix name="ChromeTabbedActivity"
       label="Activity launched in TABBED mode on Android"/>
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index def47a6..b0cda21 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -2186,6 +2186,7 @@
   text_data.id = 2;
   text_data.role = ax::mojom::Role::kStaticText;
   text_data.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "sans");
+  text_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, 16);
   text_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontWeight, 300);
   text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle, 1);
   text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle,
@@ -2369,6 +2370,11 @@
                               expected_variant);
   expected_variant.Reset();
 
+  expected_variant.Set(16.0f);
+  EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontSizeAttributeId,
+                              expected_variant);
+  expected_variant.Reset();
+
   expected_variant.Set(300);
   EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_FontWeightAttributeId,
                               expected_variant);
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 0007dbe..c913d099 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -4095,6 +4095,14 @@
       V_VT(result) = VT_BSTR;
       V_BSTR(result) = GetFontNameAttributeAsBSTR();
       break;
+    case UIA_FontSizeAttributeId:
+      V_VT(result) = VT_R8;
+      V_R8(result) = GetFloatAttribute(ax::mojom::FloatAttribute::kFontSize);
+      break;
+    case UIA_FontWeightAttributeId:
+      V_VT(result) = VT_I4;
+      V_I4(result) = GetFloatAttribute(ax::mojom::FloatAttribute::kFontWeight);
+      break;
     case UIA_ForegroundColorAttributeId:
       V_VT(result) = VT_I4;
       V_I4(result) = GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute::kColor);
diff --git a/ui/gfx/linux/native_pixmap_dmabuf.cc b/ui/gfx/linux/native_pixmap_dmabuf.cc
index 9442a7f..8327892 100644
--- a/ui/gfx/linux/native_pixmap_dmabuf.cc
+++ b/ui/gfx/linux/native_pixmap_dmabuf.cc
@@ -43,9 +43,12 @@
   return planes_[plane].offset;
 }
 
-uint64_t NativePixmapDmaBuf::GetDmaBufModifier(size_t plane) const {
-  DCHECK_LT(plane, planes_.size());
-  return planes_[plane].modifier;
+uint64_t NativePixmapDmaBuf::GetBufferFormatModifier() const {
+  // Modifiers must be the same on all the planes. Return the modifier of the
+  // first plane.
+  // TODO(crbug.com/957381): Move modifier variable to NativePixmapHandle from
+  // NativePixmapPlane.
+  return planes_[0].modifier;
 }
 
 gfx::BufferFormat NativePixmapDmaBuf::GetBufferFormat() const {
diff --git a/ui/gfx/linux/native_pixmap_dmabuf.h b/ui/gfx/linux/native_pixmap_dmabuf.h
index cea9d0c..eeca6cc5 100644
--- a/ui/gfx/linux/native_pixmap_dmabuf.h
+++ b/ui/gfx/linux/native_pixmap_dmabuf.h
@@ -32,7 +32,7 @@
   int GetDmaBufFd(size_t plane) const override;
   int GetDmaBufPitch(size_t plane) const override;
   int GetDmaBufOffset(size_t plane) const override;
-  uint64_t GetDmaBufModifier(size_t plane) const override;
+  uint64_t GetBufferFormatModifier() const override;
   gfx::BufferFormat GetBufferFormat() const override;
   gfx::Size GetBufferSize() const override;
   uint32_t GetUniqueId() const override;
diff --git a/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc b/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc
index 624f7fa..c1c9c3c 100644
--- a/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc
+++ b/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc
@@ -28,7 +28,7 @@
       const int stride = (i + 1) * image_size.width();
       const int offset = i * image_size.width() * image_size.height();
       const uint64_t size = stride * image_size.height();
-      const uint64_t modifiers = 1 << i;
+      const uint64_t modifiers = 1;
       base::ScopedFD fd(open("/dev/zero", O_RDONLY));
       EXPECT_TRUE(fd.is_valid());
 
@@ -68,7 +68,7 @@
               handle_clone.planes[i].stride);
     EXPECT_EQ(native_pixmap_dmabuf->GetDmaBufOffset(i),
               handle_clone.planes[i].offset);
-    EXPECT_EQ(native_pixmap_dmabuf->GetDmaBufModifier(i),
+    EXPECT_EQ(native_pixmap_dmabuf->GetBufferFormatModifier(),
               handle_clone.planes[i].modifier);
   }
 }
diff --git a/ui/gfx/native_pixmap.h b/ui/gfx/native_pixmap.h
index f8ba1fd..7446359 100644
--- a/ui/gfx/native_pixmap.h
+++ b/ui/gfx/native_pixmap.h
@@ -29,8 +29,11 @@
   virtual int GetDmaBufFd(size_t plane) const = 0;
   virtual int GetDmaBufPitch(size_t plane) const = 0;
   virtual int GetDmaBufOffset(size_t plane) const = 0;
-  virtual uint64_t GetDmaBufModifier(size_t plane) const = 0;
+
+  // The following methods return format, modifier and size of the buffer,
+  // respectively.
   virtual gfx::BufferFormat GetBufferFormat() const = 0;
+  virtual uint64_t GetBufferFormatModifier() const = 0;
   virtual gfx::Size GetBufferSize() const = 0;
 
   // Return an id that is guaranteed to be unique and equal for all instances
diff --git a/ui/gl/gl_image_native_pixmap.cc b/ui/gl/gl_image_native_pixmap.cc
index d8a1543b..77dc777 100644
--- a/ui/gl/gl_image_native_pixmap.cc
+++ b/ui/gl/gl_image_native_pixmap.cc
@@ -184,9 +184,9 @@
       attrs.push_back(pixmap->GetDmaBufOffset(pixmap_plane));
       attrs.push_back(EGL_DMA_BUF_PLANE0_PITCH_EXT + attrs_plane * 3);
       attrs.push_back(pixmap->GetDmaBufPitch(pixmap_plane));
+      uint64_t modifier = pixmap->GetBufferFormatModifier();
       if (has_dma_buf_import_modifier &&
-          pixmap->GetDmaBufModifier(0) != gfx::NativePixmapPlane::kNoModifier) {
-        uint64_t modifier = pixmap->GetDmaBufModifier(pixmap_plane);
+          modifier != gfx::NativePixmapPlane::kNoModifier) {
         DCHECK(attrs_plane < base::size(kLinuxDrmModifiers));
         attrs.push_back(kLinuxDrmModifiers[attrs_plane]);
         attrs.push_back(modifier & 0xffffffff);
diff --git a/ui/ozone/platform/cast/surface_factory_cast.cc b/ui/ozone/platform/cast/surface_factory_cast.cc
index 41d4389..11ccb3c 100644
--- a/ui/ozone/platform/cast/surface_factory_cast.cc
+++ b/ui/ozone/platform/cast/surface_factory_cast.cc
@@ -53,7 +53,7 @@
   int GetDmaBufFd(size_t plane) const override { return -1; }
   int GetDmaBufPitch(size_t plane) const override { return 0; }
   int GetDmaBufOffset(size_t plane) const override { return 0; }
-  uint64_t GetDmaBufModifier(size_t plane) const override { return 0; }
+  uint64_t GetBufferFormatModifier() const override { return 0; }
   gfx::BufferFormat GetBufferFormat() const override {
     return gfx::BufferFormat::BGRA_8888;
   }
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
index 2d80356..fbe77fbb4 100644
--- a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
@@ -44,7 +44,7 @@
   return buffer_->GetPlaneOffset(plane);
 }
 
-uint64_t GbmPixmap::GetDmaBufModifier(size_t plane) const {
+uint64_t GbmPixmap::GetBufferFormatModifier() const {
   return buffer_->GetFormatModifier();
 }
 
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.h b/ui/ozone/platform/drm/gpu/gbm_pixmap.h
index c10420b8..d6de7628 100644
--- a/ui/ozone/platform/drm/gpu/gbm_pixmap.h
+++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.h
@@ -29,7 +29,7 @@
   int GetDmaBufFd(size_t plane) const override;
   int GetDmaBufPitch(size_t plane) const override;
   int GetDmaBufOffset(size_t plane) const override;
-  uint64_t GetDmaBufModifier(size_t plane) const override;
+  uint64_t GetBufferFormatModifier() const override;
   gfx::BufferFormat GetBufferFormat() const override;
   gfx::Size GetBufferSize() const override;
   uint32_t GetUniqueId() const override;
diff --git a/ui/ozone/platform/headless/headless_surface_factory.cc b/ui/ozone/platform/headless/headless_surface_factory.cc
index 4a751a1a..4647a53 100644
--- a/ui/ozone/platform/headless/headless_surface_factory.cc
+++ b/ui/ozone/platform/headless/headless_surface_factory.cc
@@ -85,7 +85,7 @@
   int GetDmaBufFd(size_t plane) const override { return -1; }
   int GetDmaBufPitch(size_t plane) const override { return 0; }
   int GetDmaBufOffset(size_t plane) const override { return 0; }
-  uint64_t GetDmaBufModifier(size_t plane) const override { return 0; }
+  uint64_t GetBufferFormatModifier() const override { return 0; }
   gfx::BufferFormat GetBufferFormat() const override { return format_; }
   gfx::Size GetBufferSize() const override { return gfx::Size(); }
   uint32_t GetUniqueId() const override { return 0; }
diff --git a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
index fedc834..5ac57bbe 100644
--- a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
+++ b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
@@ -33,7 +33,7 @@
     NOTREACHED();
     return 0;
   }
-  uint64_t GetDmaBufModifier(size_t plane) const override {
+  uint64_t GetBufferFormatModifier() const override {
     NOTREACHED();
     return 0;
   }
@@ -385,4 +385,4 @@
   vk_image_info->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 }
 
-}  // namespace ui
\ No newline at end of file
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
index 960afcd..f76f08f 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
@@ -100,7 +100,7 @@
   return gbm_bo_->GetPlaneOffset(plane);
 }
 
-uint64_t GbmPixmapWayland::GetDmaBufModifier(size_t plane) const {
+uint64_t GbmPixmapWayland::GetBufferFormatModifier() const {
   return gbm_bo_->GetFormatModifier();
 }
 
@@ -149,9 +149,9 @@
   }
 
   for (size_t i = 0; i < num_planes; ++i) {
-    handle.planes.emplace_back(GetDmaBufPitch(i), GetDmaBufOffset(i),
-                               gbm_bo_->GetPlaneSize(i),
-                               std::move(scoped_fds[i]), GetDmaBufModifier(i));
+    handle.planes.emplace_back(
+        GetDmaBufPitch(i), GetDmaBufOffset(i), gbm_bo_->GetPlaneSize(i),
+        std::move(scoped_fds[i]), GetBufferFormatModifier());
   }
   return handle;
 }
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h
index b99a36d..9cb9f8f0 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h
@@ -36,7 +36,7 @@
   int GetDmaBufFd(size_t plane) const override;
   int GetDmaBufPitch(size_t plane) const override;
   int GetDmaBufOffset(size_t plane) const override;
-  uint64_t GetDmaBufModifier(size_t plane) const override;
+  uint64_t GetBufferFormatModifier() const override;
   gfx::BufferFormat GetBufferFormat() const override;
   gfx::Size GetBufferSize() const override;
   uint32_t GetUniqueId() const override;
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_list.js b/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
index 6f9646e..7a2f1fe 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_list.js
@@ -130,8 +130,7 @@
    * @private
    */
   onImportTap_: function(e) {
-    this.handleImport_(
-        false, /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget));
+    this.handleImport_(false, /** @type {!HTMLElement} */ (e.target));
   },
 
   // <if expr="chromeos">
@@ -140,8 +139,7 @@
    * @param {!Event} e
    */
   onImportAndBindTap_: function(e) {
-    this.handleImport_(
-        true, /** @type {!HTMLElement} */ (Polymer.dom(e).localTarget));
+    this.handleImport_(true, /** @type {!HTMLElement} */ (e.target));
   },
   // </if>
 
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
index fb367837..4e12334 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
+++ b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
@@ -164,7 +164,12 @@
         // If nothing selected, start from the first radio then add |delta|.
         const lastSelection = enabledRadios.findIndex(radio => radio.checked);
         selectedIndex = Math.max(0, lastSelection) + delta;
-        selectedIndex = Math.min(max, Math.max(0, selectedIndex));
+        // Wrap the selection, if needed.
+        if (selectedIndex > max) {
+          selectedIndex = 0;
+        } else if (selectedIndex < 0) {
+          selectedIndex = max;
+        }
       } else {
         return;
       }