diff --git a/.gn b/.gn
index 22e155c1..1da6987 100644
--- a/.gn
+++ b/.gn
@@ -71,7 +71,6 @@
   "//chrome/browser/updates/announcement_notification:*",  # 15 errors
   "//chrome/browser/updates/internal:*",  # 8 errors
   "//chrome/browser/updates:*",  # 21 errors
-  "//chrome/browser:*",  # 780 errors
   "//chrome/child:*",  # 3 errors
   "//chrome/install_static:*",  # 4 errors
   "//chrome/notification_helper:*",  # 4 errors
@@ -250,7 +249,6 @@
   "//third_party/blink/renderer/core/workers:*",  # 289 errors
   "//third_party/blink/renderer/core/xmlhttprequest:*",  # 49 errors
   "//third_party/blink/renderer/core:*",  # 823 errors
-  "//third_party/blink/renderer/modules/font_access:*",  # 3 errors
   "//third_party/blink/renderer/modules/peerconnection:*",  # 43 errors
 
   "//third_party/breakpad:*",  # 34 errors
diff --git a/DEPS b/DEPS
index 5db0d0cc..b8fb518 100644
--- a/DEPS
+++ b/DEPS
@@ -195,7 +195,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'a195d101f96c3d0d2c3d67b1a84d1286dce52719',
+  'skia_revision': '2610e8261e9e8be23eeb8054f8aa4a148234d214',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -207,7 +207,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '377e748714bfcb827a2a61a3b8ccf8ea8fab2156',
+  'angle_revision': '4d3a0f602852ff8277a716fd99101fefbf6d544b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -215,7 +215,7 @@
   # 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': '40d3f47d189c1ac36b14f3fe6d6ba9ce4c0a7d7e',
+  'pdfium_revision': '5b4eaf71f78bdb2e1c8b88a84eba68510dd871ac',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -242,7 +242,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': '3008f9e6de122325d8f9dbf02f7cdd51fa1ec306',
+  'nacl_revision': '69a0d6e8affc94187af10e0673592d1b238c6eb0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -258,7 +258,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '3f01e83f96b0c3cc3cd4f31bc4c097c55d13bdd1',
+  'catapult_revision': '511a82c95fc7a633cff6a3330ed82b13cdd77d41',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -266,7 +266,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'a1f84f4a730074d194fbebcf94251fe68b9395ec',
+  'devtools_frontend_revision': '97fd79e5180bff3ec46a93ae6f63f20b6d7b8248',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -545,7 +545,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'aba747b4808a48603bae4798569aff2eb7d2404d',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'eb829842242a3f8f657992381100407d205892fd',
       'condition': 'checkout_ios',
   },
 
@@ -896,7 +896,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '52fdd1ffcefb16435eee27023af95e5e844cfc16',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '8e500174f4bb7f19f1ef4630d7887146e9e14100',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1149,7 +1149,7 @@
   },
 
   'src/third_party/libvpx/source/libvpx':
-    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '53747dfe65eaf670a7192f55117f3bf1e0280743',
+    Var('chromium_git') + '/webm/libvpx.git' + '@' +  '97356acb50e212fcfb7c91715718ec70953f780c',
 
   'src/third_party/libwebm/source':
     Var('chromium_git') + '/webm/libwebm.git' + '@' + '51ca718c3adf0ddedacd7df25fe45f67dc5a9ce1',
@@ -1249,7 +1249,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b189aac831322c28c0d3d3f41cab7c0214dd8c9b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd48ef5f7ddc8d145e228a47f7297b6037a784d29',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1327,7 +1327,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': '8YjsZy1I3YIJIOUKErXW54SHjiEX62hd9SqYjmop19oC'
+              'version': 'IzByO_5k6SuUgXEKi9WBGlM_GOmnAPTcCsvgllPowHoC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1471,7 +1471,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ae073e44336fbc0f9e9f7af318447d5feebc660f',
+    Var('webrtc_git') + '/src.git' + '@' + 'b10d4a612b873d59c7ea79982c033dacfb68cd76',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1509,7 +1509,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'f-DTUcX4U7OFc5zCWXjvrSxtdJvuhXh4sUcNTJ25tGgC',
+          'version': 'KS0vcfHGwNGOUpfZjf2qjxLk_Ab7qh3iQyA7Td9WfEwC',
         },
       ],
       'dep_type': 'cipd',
@@ -1519,7 +1519,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': '0T7A2zKUcd9aOk8Bm8_upn7Ee3pt5MEMFPxJlsU5MSgC',
+          'version': 'kIrRtMsJjy2dsd5vfikJtfXXZk1zkN3lql3PtSU1kG4C',
         },
       ],
       'dep_type': 'cipd',
@@ -1529,7 +1529,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'ZEjCOhpgQq2QyPhqYBzxLzorH4ZmulpCNyb_UYP34iYC',
+          'version': 'uCrto8KqeHOJLjyX8eolAvFrtFughNiEMrlow-3BHisC',
         },
       ],
       'dep_type': 'cipd',
@@ -1543,7 +1543,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7e0dbae74fcd57f3c9ca145c2b0f979089cfc184',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@deacaf80c273290327930547aef17bfc20cb47e4',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_print_manager.cc b/android_webview/browser/aw_print_manager.cc
index 10be4f90..dda4277 100644
--- a/android_webview/browser/aw_print_manager.cc
+++ b/android_webview/browser/aw_print_manager.cc
@@ -18,14 +18,15 @@
 #include "components/printing/common/print.mojom.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
+#include "printing/print_job_constants.h"
 
 namespace android_webview {
 
 namespace {
 
-int SaveDataToFd(int fd,
-                 int page_count,
-                 scoped_refptr<base::RefCountedSharedMemoryMapping> data) {
+uint32_t SaveDataToFd(int fd,
+                      uint32_t page_count,
+                      scoped_refptr<base::RefCountedSharedMemoryMapping> data) {
   bool result = fd > base::kInvalidFd &&
                 base::IsValueInRangeForNumericType<int>(data->size());
   if (result) {
@@ -128,6 +129,12 @@
     return;
   }
 
+  if (number_pages_ > printing::kMaxPageCount) {
+    web_contents()->Stop();
+    PdfWritingDone(0);
+    return;
+  }
+
   DCHECK(pdf_writing_done_callback_);
   base::PostTaskAndReplyWithResult(
       base::ThreadPool::CreateTaskRunner(
@@ -143,9 +150,10 @@
 void AwPrintManager::OnDidPrintDocumentWritingDone(
     const PdfWritingDoneCallback& callback,
     std::unique_ptr<DelayedFrameDispatchHelper> helper,
-    int page_count) {
+    uint32_t page_count) {
+  DCHECK_LE(page_count, printing::kMaxPageCount);
   if (callback)
-    callback.Run(page_count);
+    callback.Run(base::checked_cast<int>(page_count));
   helper->SendCompleted();
 }
 
diff --git a/android_webview/browser/aw_print_manager.h b/android_webview/browser/aw_print_manager.h
index 1da74ec2..499bc52 100644
--- a/android_webview/browser/aw_print_manager.h
+++ b/android_webview/browser/aw_print_manager.h
@@ -57,7 +57,7 @@
   static void OnDidPrintDocumentWritingDone(
       const PdfWritingDoneCallback& callback,
       std::unique_ptr<DelayedFrameDispatchHelper> helper,
-      int page_count);
+      uint32_t page_count);
 
   const std::unique_ptr<printing::PrintSettings> settings_;
 
diff --git a/android_webview/renderer/aw_content_settings_client.cc b/android_webview/renderer/aw_content_settings_client.cc
index 125b3a31..77b8f2ce 100644
--- a/android_webview/renderer/aw_content_settings_client.cc
+++ b/android_webview/renderer/aw_content_settings_client.cc
@@ -58,7 +58,7 @@
 }
 
 bool AwContentSettingsClient::ShouldAutoupgradeMixedContent() {
-  return render_frame()->GetWebkitPreferences().allow_mixed_content_upgrades;
+  return render_frame()->GetBlinkPreferences().allow_mixed_content_upgrades;
 }
 
 void AwContentSettingsClient::OnDestruct() {
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 8e5dd48..a679592 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -4,16 +4,16 @@
 
 #include "ash/capture_mode/capture_mode_session.h"
 
-#include <memory>
-
 #include "ash/capture_mode/capture_mode_bar_view.h"
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "base/memory/ptr_util.h"
+#include "cc/paint/paint_flags.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_type.h"
@@ -21,8 +21,13 @@
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/scoped_canvas.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/label.h"
 
 namespace ash {
 
@@ -30,6 +35,17 @@
 
 constexpr int kBorderStrokePx = 2;
 
+// The visual radius of the drag affordance circles which are shown while
+// resizing a drag region.
+constexpr int kAffordanceCircleRadiusDp = 5;
+
+// The hit radius of the drag affordance circles touch events.
+constexpr int kAffordanceCircleTouchHitRadiusDp = 16;
+
+constexpr int kSizeLabelYDistanceFromRegionDp = 8;
+
+constexpr SkColor kRegionBorderColor = SK_ColorWHITE;
+
 // Blue300 at 30%.
 constexpr SkColor kCaptureRegionColor = SkColorSetA(gfx::kGoogleBlue300, 77);
 
@@ -49,6 +65,66 @@
   return root->GetChildById(kShellWindowId_OverlayContainer);
 }
 
+// Retrieves the point on the |rect| associated with |position|.
+gfx::Point GetLocationForPosition(const gfx::Rect& rect,
+                                  FineTunePosition position) {
+  switch (position) {
+    case FineTunePosition::kTopLeft:
+      return rect.origin();
+    case FineTunePosition::kTopCenter:
+      return rect.top_center();
+    case FineTunePosition::kTopRight:
+      return rect.top_right();
+    case FineTunePosition::kRightCenter:
+      return rect.right_center();
+    case FineTunePosition::kBottomRight:
+      return rect.bottom_right();
+    case FineTunePosition::kBottomCenter:
+      return rect.bottom_center();
+    case FineTunePosition::kBottomLeft:
+      return rect.bottom_left();
+    case FineTunePosition::kLeftCenter:
+      return rect.left_center();
+    default:
+      break;
+  }
+
+  NOTREACHED();
+  return gfx::Point();
+}
+
+// Returns the smallest rect that contains all of |points|.
+gfx::Rect GetRectEnclosingPoints(const std::vector<gfx::Point>& points) {
+  DCHECK_GE(points.size(), 2u);
+
+  int x = INT_MAX;
+  int y = INT_MAX;
+  int right = INT_MIN;
+  int bottom = INT_MIN;
+  for (const gfx::Point& point : points) {
+    x = std::min(point.x(), x);
+    y = std::min(point.y(), y);
+    right = std::max(point.x(), right);
+    bottom = std::max(point.y(), bottom);
+  }
+  return gfx::Rect(x, y, right - x, bottom - y);
+}
+
+// Returns the widget init params needed to create a widget associated with a
+// capture session.
+views::Widget::InitParams CreateWidgetParams(aura::Window* parent,
+                                             const gfx::Rect& bounds,
+                                             const std::string& name) {
+  views::Widget::InitParams params(
+      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+  params.parent = parent;
+  params.bounds = bounds;
+  params.name = name;
+  return params;
+}
+
 }  // namespace
 
 CaptureModeSession::CaptureModeSession(CaptureModeController* controller,
@@ -67,15 +143,8 @@
   parent->layer()->Add(layer());
   layer()->SetBounds(parent->bounds());
 
-  views::Widget::InitParams params(
-      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
-  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
-  params.parent = parent;
-  params.bounds = CaptureModeBarView::GetBounds(root);
-  params.name = "CaptureModeBarWidget";
-
-  capture_mode_bar_widget_.Init(std::move(params));
+  capture_mode_bar_widget_.Init(CreateWidgetParams(
+      parent, CaptureModeBarView::GetBounds(root), "CaptureModeBarWidget"));
   capture_mode_bar_widget_.SetContentsView(
       base::WrapUnique(capture_mode_bar_view_));
   capture_mode_bar_widget_.Show();
@@ -100,6 +169,7 @@
 void CaptureModeSession::OnCaptureSourceChanged(CaptureModeSource new_source) {
   capture_mode_bar_view_->OnCaptureSourceChanged(new_source);
   SetMouseWarpEnabled(new_source != CaptureModeSource::kRegion);
+  UpdateCaptureRegionWidgets();
   layer()->SchedulePaint(layer()->bounds());
 }
 
@@ -136,11 +206,22 @@
 }
 
 void CaptureModeSession::OnMouseEvent(ui::MouseEvent* event) {
-  // TODO(afakhry): Fill in here.
+  OnLocatedEvent(event, /*is_touch=*/false);
 }
 
 void CaptureModeSession::OnTouchEvent(ui::TouchEvent* event) {
-  // TODO(afakhry): Fill in here.
+  OnLocatedEvent(event, /*is_touch=*/true);
+}
+
+void CaptureModeSession::ButtonPressed(views::Button* sender,
+                                       const ui::Event& event) {
+  if (!capture_button_widget_)
+    return;
+
+  DCHECK_EQ(static_cast<views::LabelButton*>(
+                capture_button_widget_->GetContentsView()),
+            sender);
+  controller_->PerformCapture();  // |this| is destroyed here.
 }
 
 gfx::Rect CaptureModeSession::GetSelectedWindowBounds() const {
@@ -150,15 +231,12 @@
 
 void CaptureModeSession::RefreshStackingOrder(aura::Window* parent_container) {
   DCHECK(parent_container);
-  auto* widget_layer = capture_mode_bar_widget_.GetNativeWindow()->layer();
+  auto* capture_mode_bar_layer = capture_mode_bar_widget_.GetLayer();
   auto* overlay_layer = layer();
   auto* parent_container_layer = parent_container->layer();
 
-  DCHECK_EQ(parent_container_layer, overlay_layer->parent());
-  DCHECK_EQ(parent_container_layer, widget_layer->parent());
-
   parent_container_layer->StackAtTop(overlay_layer);
-  parent_container_layer->StackAtTop(widget_layer);
+  parent_container_layer->StackAtTop(capture_mode_bar_layer);
 }
 
 void CaptureModeSession::PaintCaptureRegion(gfx::Canvas* canvas) {
@@ -187,22 +265,320 @@
   const float dsf = canvas->UndoDeviceScaleFactor();
   region = gfx::ScaleToEnclosingRect(region, dsf);
 
-  canvas->FillRect(region, SK_ColorBLACK, SkBlendMode::kClear);
-  canvas->FillRect(region, kCaptureRegionColor);
+  if (!adjustable_region) {
+    canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
+    canvas->FillRect(region, kCaptureRegionColor);
+    return;
+  }
 
-  if (!adjustable_region)
+  region.Inset(-kBorderStrokePx, -kBorderStrokePx);
+  canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
+  canvas->DrawRect(gfx::RectF(region), kRegionBorderColor);
+
+  if (is_select_phase_)
     return;
 
-  // TODO(afakhry): For adjustable regions, we may change the colors. Also,
-  // paint the drag points at the corners.
-  region.Inset(-kBorderStrokePx, -kBorderStrokePx);
+  // Do not show affordance circles when repositioning the whole region.
+  if (fine_tune_position_ == FineTunePosition::kCenter)
+    return;
+
+  // Draw the drag affordance circles.
+  // TODO(sammiequon): Draw a drop shadow for the affordance circles and the
+  // border.
   cc::PaintFlags flags;
-  flags.setAntiAlias(true);
-  flags.setStyle(cc::PaintFlags::kStroke_Style);
-  // TODO(afakhry): Update to match the specs.
-  flags.setColor(gfx::kGoogleBlue200);
-  flags.setStrokeWidth(SkIntToScalar(kBorderStrokePx));
-  canvas->DrawRect(region, flags);
+  flags.setColor(kRegionBorderColor);
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  canvas->DrawCircle(region.origin(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.top_center(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.top_right(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.left_center(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.right_center(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.bottom_left(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.bottom_center(), kAffordanceCircleRadiusDp, flags);
+  canvas->DrawCircle(region.bottom_right(), kAffordanceCircleRadiusDp, flags);
+}
+
+void CaptureModeSession::OnLocatedEvent(ui::LocatedEvent* event,
+                                        bool is_touch) {
+  // No need to handle events if the current source is not region.
+  if (controller_->source() != CaptureModeSource::kRegion)
+    return;
+
+  gfx::Point location = event->location();
+  aura::Window* source = static_cast<aura::Window*>(event->target());
+  aura::Window::ConvertPointToTarget(source, current_root_, &location);
+
+  // Let the capture button handle any events within its bounds.
+  if (capture_button_widget_ &&
+      capture_button_widget_->GetNativeWindow()->bounds().Contains(location)) {
+    return;
+  }
+
+  // Allow events that are located on the capture mode bar to pass through so we
+  // can click the buttons.
+  if (!CaptureModeBarView::GetBounds(current_root_).Contains(location)) {
+    event->SetHandled();
+    event->StopPropagation();
+  }
+
+  switch (event->type()) {
+    case ui::ET_MOUSE_PRESSED:
+    case ui::ET_TOUCH_PRESSED:
+      OnLocatedEventPressed(location, is_touch);
+      break;
+    case ui::ET_MOUSE_DRAGGED:
+    case ui::ET_TOUCH_MOVED:
+      OnLocatedEventDragged(location);
+      break;
+    case ui::ET_MOUSE_RELEASED:
+    case ui::ET_TOUCH_RELEASED:
+      OnLocatedEventReleased(location);
+      break;
+    default:
+      break;
+  }
+}
+
+void CaptureModeSession::OnLocatedEventPressed(
+    const gfx::Point& location_in_root,
+    bool is_touch) {
+  initial_location_in_root_ = location_in_root;
+  previous_location_in_root_ = location_in_root;
+
+  if (is_select_phase_)
+    return;
+
+  // Calculate the position and anchor points of the current pressed event.
+  fine_tune_position_ = FineTunePosition::kNone;
+  // In the case of overlapping affordances, prioritize the bottomm right
+  // corner, then the rest of the corners, then the edges.
+  static const std::vector<FineTunePosition> drag_positions = {
+      FineTunePosition::kBottomRight,  FineTunePosition::kBottomLeft,
+      FineTunePosition::kTopLeft,      FineTunePosition::kTopRight,
+      FineTunePosition::kBottomCenter, FineTunePosition::kLeftCenter,
+      FineTunePosition::kTopCenter,    FineTunePosition::kRightCenter};
+
+  const int hit_radius =
+      is_touch ? kAffordanceCircleTouchHitRadiusDp : kAffordanceCircleRadiusDp;
+  const int hit_radius_squared = hit_radius * hit_radius;
+  for (FineTunePosition position : drag_positions) {
+    const gfx::Point position_location =
+        GetLocationForPosition(controller_->user_capture_region(), position);
+    // If |location_in_root| is within |hit_radius| of |position_location| for
+    // both x and y, then |position| is the current pressed down affordance.
+    if ((position_location - location_in_root).LengthSquared() <=
+        hit_radius_squared) {
+      fine_tune_position_ = position;
+      break;
+    }
+  }
+
+  if (fine_tune_position_ == FineTunePosition::kNone) {
+    // If the point is outside the capture region and not on the capture bar,
+    // restart to the select phase.
+    if (controller_->user_capture_region().Contains(location_in_root)) {
+      fine_tune_position_ = FineTunePosition::kCenter;
+    } else if (!CaptureModeBarView::GetBounds(current_root_)
+                    .Contains(location_in_root)) {
+      is_select_phase_ = true;
+      UpdateCaptureRegion(gfx::Rect());
+    }
+    return;
+  }
+
+  anchor_points_ = GetAnchorPointsForPosition(fine_tune_position_);
+}
+
+void CaptureModeSession::OnLocatedEventDragged(
+    const gfx::Point& location_in_root) {
+  const gfx::Point previous_location_in_root = previous_location_in_root_;
+  previous_location_in_root_ = location_in_root;
+
+  // For the select phase, the select region is the rectangle formed by the
+  // press location and the current locatiion.
+  if (is_select_phase_) {
+    UpdateCaptureRegion(
+        GetRectEnclosingPoints({initial_location_in_root_, location_in_root}));
+    return;
+  }
+
+  if (fine_tune_position_ == FineTunePosition::kNone)
+    return;
+
+  // For a reposition, offset the old select region by the difference between
+  // the current location and the previous location, but do not let the select
+  // region go offscreen.
+  if (fine_tune_position_ == FineTunePosition::kCenter) {
+    gfx::Rect new_capture_region = controller_->user_capture_region();
+    new_capture_region.Offset(location_in_root - previous_location_in_root);
+    new_capture_region.AdjustToFit(current_root_->bounds());
+    UpdateCaptureRegion(new_capture_region);
+    return;
+  }
+
+  // The new region is defined by the rectangle which encloses the anchor
+  // point(s) and |location_in_root|.
+  std::vector<gfx::Point> points = anchor_points_;
+  DCHECK(!points.empty());
+  points.push_back(location_in_root);
+  UpdateCaptureRegion(GetRectEnclosingPoints(points));
+}
+
+void CaptureModeSession::OnLocatedEventReleased(
+    const gfx::Point& location_in_root) {
+  fine_tune_position_ = FineTunePosition::kNone;
+  anchor_points_.clear();
+
+  // Do a repaint to show the affordance circles. See UpdateCaptureRegion to see
+  // how damage is calculated.
+  gfx::Rect damage_region = controller_->user_capture_region();
+  damage_region.Inset(
+      gfx::Insets(-kAffordanceCircleRadiusDp - kBorderStrokePx));
+  layer()->SchedulePaint(damage_region);
+
+  if (!is_select_phase_)
+    return;
+
+  // After first release event, we advance to the next phase.
+  is_select_phase_ = false;
+  UpdateCaptureRegionWidgets();
+}
+
+void CaptureModeSession::UpdateCaptureRegion(
+    const gfx::Rect& new_capture_region) {
+  const gfx::Rect old_capture_region = controller_->user_capture_region();
+  if (old_capture_region == new_capture_region)
+    return;
+
+  // Calculate the region that has been damaged and repaint the layer. Add some
+  // extra padding to make sure the border and affordance circles are also
+  // repainted.
+  gfx::Rect damage_region = old_capture_region;
+  damage_region.Union(new_capture_region);
+  damage_region.Inset(
+      gfx::Insets(-kAffordanceCircleRadiusDp - kBorderStrokePx));
+  layer()->SchedulePaint(damage_region);
+
+  controller_->set_user_capture_region(new_capture_region);
+  UpdateCaptureRegionWidgets();
+}
+
+void CaptureModeSession::UpdateCaptureRegionWidgets() {
+  // TODO(sammiequon): The dimensons label is always shown and the capture
+  // button label is always shown in the fine tune stage. Update this to match
+  // the specs.
+  const bool show = controller_->source() == CaptureModeSource::kRegion;
+  if (!show) {
+    dimensions_label_widget_.reset();
+    capture_button_widget_.reset();
+    return;
+  }
+
+  // TODO(sammiequon): Add styling to the two widget content views. Also, the
+  // widgets should be repositioned if the region is too small or too close to
+  // the edge.
+  const gfx::Rect capture_region = controller_->user_capture_region();
+  if (!dimensions_label_widget_) {
+    auto* parent = GetParentContainer(current_root_);
+    dimensions_label_widget_ = std::make_unique<views::Widget>();
+    dimensions_label_widget_->Init(
+        CreateWidgetParams(parent, gfx::Rect(), "CaptureModeSizeLabel"));
+    dimensions_label_widget_->SetContentsView(std::make_unique<views::Label>());
+    dimensions_label_widget_->Show();
+    parent->StackChildBelow(dimensions_label_widget_->GetNativeWindow(),
+                            capture_mode_bar_widget_.GetNativeWindow());
+  }
+
+  // Update the location of the size label. It is in the center of the region
+  // horizontally and slightly below the region vertically.
+  views::Label* size_label =
+      static_cast<views::Label*>(dimensions_label_widget_->GetContentsView());
+  size_label->SetText(base::UTF8ToUTF16(base::StringPrintf(
+      "%d x %d", capture_region.width(), capture_region.height())));
+  gfx::Rect dimensions_label_widget_bounds(size_label->GetPreferredSize());
+  dimensions_label_widget_bounds.set_x(capture_region.CenterPoint().x() -
+                                       dimensions_label_widget_bounds.width() /
+                                           2);
+  dimensions_label_widget_bounds.set_y(capture_region.bottom() +
+                                       kSizeLabelYDistanceFromRegionDp);
+  dimensions_label_widget_->SetBounds(dimensions_label_widget_bounds);
+
+  if (!capture_button_widget_ && !is_select_phase_) {
+    auto* parent = GetParentContainer(current_root_);
+    capture_button_widget_ = std::make_unique<views::Widget>();
+    capture_button_widget_->Init(
+        CreateWidgetParams(parent, gfx::Rect(), "CaptureModeButton"));
+    // TODO(sammiequon): Add the localized label.
+    auto label_button =
+        std::make_unique<views::LabelButton>(this, base::string16());
+    label_button->SetImage(
+        views::Button::STATE_NORMAL,
+        gfx::CreateVectorIcon(controller_->type() == CaptureModeType::kImage
+                                  ? kCaptureModeImageIcon
+                                  : kCaptureModeVideoIcon,
+                              SK_ColorBLACK));
+    capture_button_widget_->SetContentsView(std::move(label_button));
+    capture_button_widget_->Show();
+    parent->StackChildBelow(capture_button_widget_->GetNativeWindow(),
+                            capture_mode_bar_widget_.GetNativeWindow());
+  }
+
+  if (!capture_button_widget_)
+    return;
+
+  // Update the location of the capture button.
+  views::LabelButton* capture_button = static_cast<views::LabelButton*>(
+      capture_button_widget_->GetContentsView());
+  gfx::Rect capture_button_widget_bounds = capture_region;
+  capture_button_widget_bounds.ClampToCenteredSize(
+      capture_button->GetPreferredSize());
+  capture_button_widget_->SetBounds(capture_button_widget_bounds);
+}
+
+std::vector<gfx::Point> CaptureModeSession::GetAnchorPointsForPosition(
+    FineTunePosition position) {
+  std::vector<gfx::Point> anchor_points;
+  // For a vertex, the anchor point is the opposite vertex on the rectangle
+  // (ex. bottom left vertex -> top right vertex anchor point). For an edge, the
+  // anchor points are the two vertices of the opposite edge (ex. bottom edge ->
+  // top left and top right anchor points).
+  const gfx::Rect rect = controller_->user_capture_region();
+  switch (position) {
+    case FineTunePosition::kNone:
+    case FineTunePosition::kCenter:
+      break;
+    case FineTunePosition::kTopLeft:
+      anchor_points.push_back(rect.bottom_right());
+      break;
+    case FineTunePosition::kTopCenter:
+      anchor_points.push_back(rect.bottom_left());
+      anchor_points.push_back(rect.bottom_right());
+      break;
+    case FineTunePosition::kTopRight:
+      anchor_points.push_back(rect.bottom_left());
+      break;
+    case FineTunePosition::kLeftCenter:
+      anchor_points.push_back(rect.top_right());
+      anchor_points.push_back(rect.bottom_right());
+      break;
+    case FineTunePosition::kRightCenter:
+      anchor_points.push_back(rect.origin());
+      anchor_points.push_back(rect.bottom_left());
+      break;
+    case FineTunePosition::kBottomLeft:
+      anchor_points.push_back(rect.top_right());
+      break;
+    case FineTunePosition::kBottomCenter:
+      anchor_points.push_back(rect.origin());
+      anchor_points.push_back(rect.top_right());
+      break;
+    case FineTunePosition::kBottomRight:
+      anchor_points.push_back(rect.origin());
+      break;
+  }
+  DCHECK(!anchor_points.empty());
+  DCHECK_LE(anchor_points.size(), 2u);
+  return anchor_points;
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index c015de8..e31f8b2 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -5,11 +5,15 @@
 #ifndef ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_
 #define ASH_CAPTURE_MODE_CAPTURE_MODE_SESSION_H_
 
+#include <memory>
+
 #include "ash/ash_export.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ui/compositor/layer_delegate.h"
 #include "ui/compositor/layer_owner.h"
+#include "ui/events/event.h"
 #include "ui/events/event_handler.h"
+#include "ui/views/controls/button/button.h"
 #include "ui/views/widget/widget.h"
 
 namespace gfx {
@@ -30,7 +34,8 @@
 // the one that will be.
 class ASH_EXPORT CaptureModeSession : public ui::LayerOwner,
                                       public ui::LayerDelegate,
-                                      public ui::EventHandler {
+                                      public ui::EventHandler,
+                                      public views::ButtonListener {
  public:
   // Creates the bar widget on the given |root| window.
   CaptureModeSession(CaptureModeController* controller, aura::Window* root);
@@ -61,6 +66,9 @@
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnTouchEvent(ui::TouchEvent* event) override;
 
+  // views::ButtonListener:
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
  private:
   // Gets the bounds of current window selected for |kWindow| capture source.
   gfx::Rect GetSelectedWindowBounds() const;
@@ -73,6 +81,34 @@
   // Paints the current capture region depending on the current capture source.
   void PaintCaptureRegion(gfx::Canvas* canvas);
 
+  // Helper to unify mouse/touch events. Forwards events to the three below
+  // functions and they are located on |capture_button_widget_|. Blocks events
+  // from reaching other handlers, unless the event is located on
+  // |capture_mode_bar_widget_|. |is_touch| signifies this is a touch event, and
+  // we will use larger hit targets for the drag affordances.
+  void OnLocatedEvent(ui::LocatedEvent* event, bool is_touch);
+
+  // Handles updating the select region UI.
+  void OnLocatedEventPressed(const gfx::Point& location_in_root, bool is_touch);
+  void OnLocatedEventDragged(const gfx::Point& location_in_root);
+  void OnLocatedEventReleased(const gfx::Point& location_in_root);
+
+  // Updates the capture region and the capture region widgets.
+  void UpdateCaptureRegion(const gfx::Rect& new_capture_region);
+
+  // Updates the widgets that are used to display text/icons while selecting a
+  // capture region. They are not visible during fullscreen or window capture,
+  // and some are only visible during certain phases of region capture. This
+  // will create or destroy the widgets as needed.
+  void UpdateCaptureRegionWidgets();
+
+  // Retrieves the anchor points on the current selected region associated with
+  // |position|. The anchor points are described as the points that do not
+  // change when resizing the capture region while dragging one of the drag
+  // affordances. There is one anchor point if |position| is a vertex, and two
+  // anchor points if |position| is an edge.
+  std::vector<gfx::Point> GetAnchorPointsForPosition(FineTunePosition position);
+
   CaptureModeController* const controller_;
 
   // The current root window on which the capture session is active, which may
@@ -84,6 +120,25 @@
   // The content view of the above widget and owned by its views hierarchy.
   CaptureModeBarView* capture_mode_bar_view_;
 
+  // Widgets which display text and icons during a region capture session.
+  std::unique_ptr<views::Widget> dimensions_label_widget_;
+  std::unique_ptr<views::Widget> capture_button_widget_;
+
+  // Stores the data needed to select a region during a region capture session.
+  // There are two phases for a region capture session. The select phase, where
+  // the user can quickly select a region and the fine tune phase, where the
+  // user can reposition and resize the region with a lot of accuracy.
+  bool is_select_phase_ = true;
+  // The location of the last press and drag events.
+  gfx::Point initial_location_in_root_;
+  gfx::Point previous_location_in_root_;
+  // The position of the last press event during the fine tune phase drag.
+  FineTunePosition fine_tune_position_;
+  // The points that do not change during a fine tune resize. This is empty
+  // when |fine_tune_position_| is kNone or kCenter, or if there is no drag
+  // underway.
+  std::vector<gfx::Point> anchor_points_;
+
   // Caches the old status of mouse warping before the session started to be
   // restored at the end.
   bool old_mouse_warp_status_;
diff --git a/ash/capture_mode/capture_mode_types.h b/ash/capture_mode/capture_mode_types.h
index 4248c64..db81acb 100644
--- a/ash/capture_mode/capture_mode_types.h
+++ b/ash/capture_mode/capture_mode_types.h
@@ -20,6 +20,28 @@
   kWindow,
 };
 
+// The position of the press event during the fine tune phase of a region
+// capture session. This will determine what subsequent drag events do to the
+// select region.
+enum class FineTunePosition {
+  // The initial press was outside region. Subsequent drags will do nothing.
+  kNone,
+  // The initial press was inside the select region. Subsequent drags will
+  // move the entire region.
+  kCenter,
+  // The initial press was on one of the drag affordance circles. Subsequent
+  // drags will resize the region. These are sorted clockwise starting at the
+  // top left.
+  kTopLeft,
+  kTopCenter,
+  kTopRight,
+  kRightCenter,
+  kBottomRight,
+  kBottomCenter,
+  kBottomLeft,
+  kLeftCenter,
+};
+
 }  // namespace ash
 
 #endif  // ASH_CAPTURE_MODE_CAPTURE_MODE_TYPES_H_
diff --git a/ash/system/holding_space/holding_space_item_context_menu.cc b/ash/system/holding_space/holding_space_item_context_menu.cc
index bd33ea9..204bdbf 100644
--- a/ash/system/holding_space/holding_space_item_context_menu.cc
+++ b/ash/system/holding_space/holding_space_item_context_menu.cc
@@ -4,12 +4,14 @@
 
 #include "ash/system/holding_space/holding_space_item_context_menu.h"
 
+#include "ash/public/cpp/holding_space/holding_space_client.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_controller.h"
 #include "ash/public/cpp/holding_space/holding_space_model.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/holding_space/holding_space_item_view.h"
+#include "base/bind.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/views/controls/menu/menu_runner.h"
@@ -45,16 +47,18 @@
                                                  int event_flags) {
   switch (command_id) {
     case HoldingSpaceCommandId::kCopyToClipboard:
-      // TODO(crbug.com/1127240): Hookup API for copy to clipboard
+      HoldingSpaceController::Get()->client()->CopyToClipboard(
+          *item_, base::DoNothing());
       break;
     case HoldingSpaceCommandId::kPinItem:
-      // TODO(crbug.com/1127240): Hookup API for toggling pin
+      HoldingSpaceController::Get()->client()->PinItem(*item_);
       break;
     case HoldingSpaceCommandId::kShowInFolder:
-      // TODO(crbug.com/1127240): Hookup API for show in folder
+      HoldingSpaceController::Get()->client()->OpenItemInFolder(
+          *item_, base::DoNothing());
       break;
     case HoldingSpaceCommandId::kUnpinItem:
-      // TODO(crbug.com/1127240): Hookup API for toggling pin
+      HoldingSpaceController::Get()->client()->UnpinItem(*item_);
       break;
   }
 }
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index f37a8c90..0f5efc3d 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -163,13 +163,13 @@
 }
 
 const Desk* DesksController::GetTargetActiveDesk() const {
-  if (!animations_.empty())
-    return desks_[animations_.back()->ending_desk_index()].get();
+  if (animation_)
+    return desks_[animation_->ending_desk_index()].get();
   return active_desk();
 }
 
 void DesksController::Shutdown() {
-  animations_.clear();
+  animation_.reset();
 }
 
 void DesksController::AddObserver(Observer* observer) {
@@ -181,7 +181,7 @@
 }
 
 bool DesksController::AreDesksBeingModified() const {
-  return are_desks_being_modified_ || !animations_.empty();
+  return are_desks_being_modified_ || !!animation_;
 }
 
 bool DesksController::CanCreateDesks() const {
@@ -256,9 +256,9 @@
         current_desk_index + ((current_desk_index > 0) ? -1 : 1);
     DCHECK_GE(target_desk_index, 0);
     DCHECK_LT(target_desk_index, static_cast<int>(desks_.size()));
-    animations_.emplace_back(std::make_unique<DeskRemovalAnimation>(
-        this, current_desk_index, target_desk_index, source));
-    animations_.back()->Launch();
+    animation_ = std::make_unique<DeskRemovalAnimation>(
+        this, current_desk_index, target_desk_index, source);
+    animation_->Launch();
     return;
   }
 
@@ -267,7 +267,7 @@
 
 void DesksController::ActivateDesk(const Desk* desk, DesksSwitchSource source) {
   DCHECK(HasDesk(desk));
-  DCHECK(animations_.empty());
+  DCHECK(!animation_);
 
   OverviewController* overview_controller = Shell::Get()->overview_controller();
   const bool in_overview = overview_controller->InOverviewSession();
@@ -300,9 +300,9 @@
   }
 
   const int starting_desk_index = GetDeskIndex(active_desk());
-  animations_.emplace_back(std::make_unique<DeskActivationAnimation>(
-      this, starting_desk_index, target_desk_index, source));
-  animations_.back()->Launch();
+  animation_ = std::make_unique<DeskActivationAnimation>(
+      this, starting_desk_index, target_desk_index, source);
+  animation_->Launch();
 }
 
 bool DesksController::ActivateAdjacentDesk(bool going_left,
@@ -316,15 +316,9 @@
     return false;
 
   // Try replacing an ongoing desk animation of the same source.
-  if (is_enhanced_desk_animations_) {
-    for (const auto& animation : animations_) {
-      if (animation->Replace(going_left, source))
-        return true;
-    }
-    // If there is no current animation of the same source but there is an
-    // animation running, skip this animation.
-    if (!animations_.empty())
-      return false;
+  if (is_enhanced_desk_animations_ && animation_ &&
+      animation_->Replace(going_left, source)) {
+    return true;
   }
 
   const Desk* desk_to_activate = going_left ? GetPreviousDesk() : GetNextDesk();
@@ -349,18 +343,14 @@
 
 void DesksController::UpdateAnimationForGesture(float scroll_delta_x) {
   DCHECK(is_enhanced_desk_animations_);
-  for (const auto& animation : animations_) {
-    if (animation->Update(scroll_delta_x))
-      return;
-  }
+  if (animation_)
+    animation_->Update(scroll_delta_x);
 }
 
 void DesksController::EndAnimationForGesture() {
   DCHECK(is_enhanced_desk_animations_);
-  for (const auto& animation : animations_) {
-    if (animation->End())
-      return;
-  }
+  if (animation_)
+    animation_->End();
 }
 
 bool DesksController::MoveWindowFromActiveDeskTo(
@@ -503,7 +493,8 @@
 }
 
 void DesksController::OnAnimationFinished(DeskAnimationBase* animation) {
-  base::EraseIf(animations_, base::MatchesUniquePtr(animation));
+  DCHECK_EQ(animation_.get(), animation);
+  animation_.reset();
 }
 
 bool DesksController::HasDesk(const Desk* desk) const {
diff --git a/ash/wm/desks/desks_controller.h b/ash/wm/desks/desks_controller.h
index a95da23..ce43749 100644
--- a/ash/wm/desks/desks_controller.h
+++ b/ash/wm/desks/desks_controller.h
@@ -230,9 +230,8 @@
   // mode as a result of desks modifications.
   bool are_desks_being_modified_ = false;
 
-  // List of on-going desks animations.
-  // TODO(sammiequon): Investigate if this needs to still be a list.
-  std::vector<std::unique_ptr<DeskAnimationBase>> animations_;
+  // Not null if there is an on-going desks animation.
+  std::unique_ptr<DeskAnimationBase> animation_;
 
   // A free list of desk container IDs to be used for newly-created desks. New
   // desks pops from this queue and removed desks's associated container IDs are
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 25bdb95..cc629fe 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3638,6 +3638,8 @@
       "android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
       "android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
       "android/java/src/org/chromium/base/supplier/OneShotCallback.java",
+      "android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
+      "android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
       "android/java/src/org/chromium/base/supplier/Supplier.java",
       "android/java/src/org/chromium/base/task/AsyncTask.java",
       "android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
@@ -3882,6 +3884,7 @@
       "android/junit/src/org/chromium/base/process_launcher/ChildProcessConnectionTest.java",
       "android/junit/src/org/chromium/base/supplier/ObservableSupplierImplTest.java",
       "android/junit/src/org/chromium/base/supplier/OneShotCallbackTest.java",
+      "android/junit/src/org/chromium/base/supplier/OneshotSupplierImplTest.java",
       "android/junit/src/org/chromium/base/task/AsyncTaskThreadTest.java",
       "android/junit/src/org/chromium/base/task/TaskTraitsTest.java",
       "android/junit/src/org/chromium/base/util/GarbageCollectionTestUtilsUnitTest.java",
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index 903e0c0..c2ebd3c 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -577,7 +577,30 @@
     if (size > kMaxDirectMapped) {
       if (return_null)
         return nullptr;
+      // The lock is here to protect PA from:
+      // 1. Concurrent calls
+      // 2. Reentrant calls
+      //
+      // This is fine here however, as:
+      // 1. Concurrency: |PartitionRoot::OutOfMemory()| never returns, so the
+      //    lock will not be re-acquired, which would lead to acting on
+      //    inconsistent data that could have been modified in-between releasing
+      //    and acquiring it.
+      // 2. Reentrancy: This is why we release the lock. On some platforms,
+      //    terminating the process may free() memory, or even possibly try to
+      //    allocate some. Calling free() is fine, but will deadlock since
+      //    |PartitionRoot::lock_| is not recursive.
+      //
+      // Supporting reentrant calls properly is hard, and not a requirement for
+      // PA. However up to that point, we've only *read* data, not *written* to
+      // any state. Reentrant calls are then fine, especially as we don't
+      // continue on this path. The only downside is possibly endless recursion
+      // if the OOM handler allocates and fails to use UncheckedMalloc() or
+      // equivalent, but that's violating the contract of
+      // base::OnNoMemoryInternal().
+      ScopedUnlockGuard<thread_safe> unlock{root->lock_};
       PartitionExcessiveAllocationSize(size);
+      IMMEDIATE_CRASH();  // Not required, kept as documentation.
     }
     new_page = PartitionDirectMap(root, flags, size);
     if (new_page)
@@ -636,7 +659,10 @@
               PartitionPage<thread_safe>::get_sentinel_page());
     if (return_null)
       return nullptr;
+    // See comment above.
+    ScopedUnlockGuard<thread_safe> unlock{root->lock_};
     root->OutOfMemory(size);
+    IMMEDIATE_CRASH();  // Not required, kept as documentation.
   }
 
   PA_DCHECK(new_page_bucket != get_sentinel_bucket());
diff --git a/base/allocator/partition_allocator/partition_lock.h b/base/allocator/partition_allocator/partition_lock.h
index 9167697..b2f8484 100644
--- a/base/allocator/partition_allocator/partition_lock.h
+++ b/base/allocator/partition_allocator/partition_lock.h
@@ -39,6 +39,20 @@
   MaybeSpinLock<thread_safe>& lock_;
 };
 
+template <bool thread_safe>
+class SCOPED_LOCKABLE ScopedUnlockGuard {
+ public:
+  explicit ScopedUnlockGuard(MaybeSpinLock<thread_safe>& lock)
+      UNLOCK_FUNCTION(lock)
+      : lock_(lock) {
+    lock_.Unlock();
+  }
+  ~ScopedUnlockGuard() EXCLUSIVE_LOCK_FUNCTION() { lock_.Lock(); }
+
+ private:
+  MaybeSpinLock<thread_safe>& lock_;
+};
+
 #if !DCHECK_IS_ON()
 // Spinlock. Do not use, to be removed. crbug.com/1061437.
 class BASE_EXPORT SpinLock {
diff --git a/base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java b/base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java
new file mode 100644
index 0000000..6136cdf
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java
@@ -0,0 +1,47 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.supplier;
+
+import org.chromium.base.Callback;
+
+/**
+ * OneshotSupplier wraps an asynchronously provided, non-null object {@code T}, notifying
+ * observers a single time when the dependency becomes available. Note that null is the un-set
+ * value; a fulfilled supplier will never have a null underlying value.
+ *
+ * <p>This allows classes dependent on {@code T} to be provided with a
+ * OneshotSupplier during construction and register a {@code Callback<T>} to be notified when the
+ * needed dependency is available, but without the need to un-register that Callback upon
+ * destruction. Contrast to {@link ObservableSupplier}, which requires un-registration to prevent
+ * post-destruction callback invocation because the object can change an arbitrary number of times.
+ *
+ *
+ * <p>This class must only be accessed from a single thread. Unless a particular thread designation
+ * is given by the owner of the OneshotSupplier, clients should assume it must only be accessed on
+ * the UI thread.
+ *
+ * <p>If you want to create a supplier, see an implementation in {@link OneshotSupplierImpl}.
+ *
+ * <p>For classes using a OneshotSupplier to receive a dependency:
+ * <ul>
+ *    <li>To be notified when the object is available, call {@link #onAvailable(Callback)}.
+ *    <li>If the object is already available, the Callback will be posted immediately on the handler
+ *    for the thread the supplier was created on.
+ *    <li>The Callback will be called at most
+ *    once. It's still recommended to use {@link org.chromium.base.CallbackController} for safety.
+ * </ul>
+ *
+ * @param <T> The type of the wrapped object.
+ */
+public interface OneshotSupplier<T> extends Supplier<T> {
+    /**
+     * Add a callback that's called when the object owned by this supplier is available.
+     * If the object is already available, the callback will be called at the end of the
+     * current message loop.
+     *
+     * @param callback The callback to be called.
+     */
+    void onAvailable(Callback<T> callback);
+}
diff --git a/base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java b/base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java
new file mode 100644
index 0000000..ed233d1
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java
@@ -0,0 +1,57 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.supplier;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Callback;
+import org.chromium.base.Promise;
+import org.chromium.base.ThreadUtils;
+
+/**
+ * Concrete implementation of {@link OneshotSupplier} to be used by classes owning a
+ * OneshotSupplier and providing it as a dependency to others.
+ *
+ * <p>Instances of this class must only be accessed from the thread they were created on.
+ *
+ * To use:
+ * <ol>
+ *    <li>Create a new {@code OneshotSupplierImpl<T>} to pass as a dependency.
+ *    <li>Call {@link #set(Object)} when the object becomes available. {@link #set(Object)} may only
+ * be called once.
+ * </ol>
+ *
+ * @param <T> The type of the wrapped object.
+ */
+public class OneshotSupplierImpl<T> implements OneshotSupplier<T> {
+    private final Promise<T> mPromise = new Promise<>();
+    private final ThreadUtils.ThreadChecker mThreadChecker = new ThreadUtils.ThreadChecker();
+
+    @Override
+    public void onAvailable(Callback<T> callback) {
+        mThreadChecker.assertOnValidThread();
+        mPromise.then(callback);
+    }
+
+    @Override
+    public @Nullable T get() {
+        mThreadChecker.assertOnValidThread();
+        return mPromise.isFulfilled() ? mPromise.getResult() : null;
+    }
+
+    /**
+     * Set the object supplied by this supplier. This will notify registered callbacks that the
+     * dependency is available. If set() has already been called, this method will assert.
+     *
+     * @param object The object to supply.
+     */
+    public void set(@NonNull T object) {
+        mThreadChecker.assertOnValidThread();
+        assert !mPromise.isFulfilled();
+        assert object != null;
+        mPromise.fulfill(object);
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/supplier/OneshotSupplierImplTest.java b/base/android/junit/src/org/chromium/base/supplier/OneshotSupplierImplTest.java
new file mode 100644
index 0000000..748ce01
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/supplier/OneshotSupplierImplTest.java
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowProcess;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * Unit tests for {@link OneshotSupplierImpl}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(shadows = {ShadowProcess.class})
+public class OneshotSupplierImplTest {
+    private OneshotSupplierImpl<String> mSupplier = new OneshotSupplierImpl<>();
+
+    @Spy
+    private Callback<String> mCallback1;
+    @Spy
+    private Callback<String> mCallback2;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testSet() {
+        mSupplier.onAvailable(mCallback1);
+        mSupplier.onAvailable(mCallback2);
+        mSupplier.set("answer");
+
+        verify(mCallback1).onResult("answer");
+        verify(mCallback2).onResult("answer");
+    }
+
+    @Test
+    public void testSetBeforeAddObserver() {
+        mSupplier.set("answer");
+
+        mSupplier.onAvailable(mCallback1);
+        mSupplier.onAvailable(mCallback2);
+
+        verify(mCallback1).onResult("answer");
+        verify(mCallback2).onResult("answer");
+    }
+
+    @Test
+    public void testInterleaved() {
+        mSupplier.onAvailable(mCallback1);
+        mSupplier.set("answer");
+        mSupplier.onAvailable(mCallback2);
+
+        verify(mCallback1).onResult("answer");
+        verify(mCallback2).onResult("answer");
+    }
+
+    @Test
+    public void testGet() {
+        assertNull(mSupplier.get());
+        mSupplier.set("answer");
+        assertEquals("answer", mSupplier.get());
+    }
+}
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index ef97e531..87bdf6c 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200915.2.1
+0.20200916.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index ef97e531..87bdf6c 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200915.2.1
+0.20200916.1.1
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index 781ad45..b41fb25 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -6219,6 +6219,22 @@
   CheckCanUseLCDText(LCDTextDisallowedReason::kNone, "no filter");
 }
 
+TEST_P(LCDTextTest, FilterAnimation) {
+  FilterOperations blur_filter;
+  blur_filter.Append(FilterOperation::CreateBlurFilter(4.0f));
+  SetFilter(layer_, blur_filter);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kPixelOrColorEffect, "filter");
+
+  GetEffectNode(layer_)->has_potential_filter_animation = true;
+  SetFilter(layer_, FilterOperations());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kPixelOrColorEffect,
+                     "filter animation");
+
+  GetEffectNode(layer_)->has_potential_filter_animation = false;
+  SetFilter(layer_, FilterOperations());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, "no filter");
+}
+
 TEST_P(LCDTextTest, BackdropFilter) {
   FilterOperations backdrop_filter;
   backdrop_filter.Append(FilterOperation::CreateBlurFilter(4.0f));
@@ -6235,6 +6251,28 @@
                      layer_);
 }
 
+TEST_P(LCDTextTest, BackdropFilterAnimation) {
+  FilterOperations backdrop_filter;
+  backdrop_filter.Append(FilterOperation::CreateBlurFilter(4.0f));
+  SetBackdropFilter(descendant_, backdrop_filter);
+  UpdateDrawProperties(host_impl()->active_tree());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kPixelOrColorEffect,
+                     "backdrop-filter", layer_);
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, "backdrop-filter",
+                     descendant_);
+
+  GetEffectNode(descendant_)->has_potential_backdrop_filter_animation = true;
+  SetBackdropFilter(descendant_, FilterOperations());
+  UpdateDrawProperties(host_impl()->active_tree());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kPixelOrColorEffect,
+                     "backdrop-filter animation", layer_);
+
+  GetEffectNode(descendant_)->has_potential_backdrop_filter_animation = false;
+  UpdateDrawProperties(host_impl()->active_tree());
+  CheckCanUseLCDText(LCDTextDisallowedReason::kNone, "no backdrop-filter",
+                     layer_);
+}
+
 TEST_P(LCDTextTest, ContentsOpaqueForText) {
   layer_->SetContentsOpaque(false);
   layer_->SetBackgroundColor(SK_ColorGREEN);
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index a2e21b1..1e007fd 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -777,7 +777,8 @@
     } else {
       node->target_id = effect_tree->parent(node)->target_id;
     }
-    if (!node->backdrop_filters.IsEmpty())
+    if (!node->backdrop_filters.IsEmpty() ||
+        node->has_potential_backdrop_filter_animation)
       last_backdrop_filter = node->id;
     node->affected_by_backdrop_filter = false;
   }
@@ -795,7 +796,8 @@
       current_target_id = kInvalidNodeId;
     // While down to kContentsRootNodeId, move |current_target_id| forward if
     // |node| has backdrop filter.
-    if (!node->backdrop_filters.IsEmpty() &&
+    if ((!node->backdrop_filters.IsEmpty() ||
+         node->has_potential_backdrop_filter_animation) &&
         current_target_id == kInvalidNodeId)
       current_target_id = node->target_id;
   }
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index a7d58fe..a4a770d 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -743,7 +743,8 @@
 }
 
 void EffectTree::UpdateHasFilters(EffectNode* node, EffectNode* parent_node) {
-  node->node_or_ancestor_has_filters = !node->filters.IsEmpty();
+  node->node_or_ancestor_has_filters =
+      !node->filters.IsEmpty() || node->has_potential_filter_animation;
   if (parent_node) {
     node->node_or_ancestor_has_filters |=
         parent_node->node_or_ancestor_has_filters;
diff --git a/chrome/VERSION b/chrome/VERSION
index ec3c961..cce142a 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=87
 MINOR=0
-BUILD=4265
+BUILD=4266
 PATCH=0
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediator.java
new file mode 100644
index 0000000..0c0201a
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediator.java
@@ -0,0 +1,266 @@
+// 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.
+
+package org.chromium.chrome.browser.tasks.tab_management;
+
+import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.CallbackController;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
+import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabCreationState;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
+import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.List;
+
+/**
+ * A mediator for the TabGroupPopUi. Responsible for managing the
+ * internal state of the component.
+ */
+public class TabGroupPopupUiMediator {
+    /**
+     * An interface to update the size of the TabGroupPopupUi.
+     */
+    public interface TabGroupPopUiUpdater {
+        /**
+         * Update the TabGroupPopUi.
+         */
+        void updateTabGroupPopUi();
+    }
+
+    private final PropertyModel mModel;
+    private final TabModelObserver mTabModelObserver;
+    private final TabModelSelector mTabModelSelector;
+    private final BrowserControlsStateProvider mBrowserControlsStateProvider;
+    private final BrowserControlsStateProvider.Observer mBrowserControlsObserver;
+    private final KeyboardVisibilityDelegate.KeyboardVisibilityListener mKeyboardVisibilityListener;
+    private final TabGroupPopUiUpdater mUiUpdater;
+    private final TabGroupUiMediator.TabGroupUiController mTabGroupUiController;
+    private final BottomSheetController mBottomSheetController;
+    private final BottomSheetObserver mBottomSheetObserver;
+
+    private final OverviewModeBehavior.OverviewModeObserver mOverviewModeObserver;
+    private CallbackController mCallbackController = new CallbackController();
+    private OverviewModeBehavior mOverviewModeBehavior;
+
+    private boolean mIsOverviewModeVisible;
+
+    TabGroupPopupUiMediator(PropertyModel model, TabModelSelector tabModelSelector,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            BrowserControlsStateProvider browserControlsStateProvider, TabGroupPopUiUpdater updater,
+            TabGroupUiMediator.TabGroupUiController controller,
+            BottomSheetController bottomSheetController) {
+        mModel = model;
+        mTabModelSelector = tabModelSelector;
+        mBrowserControlsStateProvider = browserControlsStateProvider;
+        mUiUpdater = updater;
+        mTabGroupUiController = controller;
+        mBottomSheetController = bottomSheetController;
+
+        mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
+            @Override
+            public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
+                    int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
+                // Modify the alpha the strip container view base on bottomOffset. The range of
+                // bottomOffset is within 0 to mIconSize.
+                mModel.set(TabGroupPopupUiProperties.CONTENT_VIEW_ALPHA,
+                        1 - mBrowserControlsStateProvider.getBrowserControlHiddenRatio());
+            }
+        };
+        mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver);
+
+        mTabModelObserver = new TabModelObserver() {
+            @Override
+            public void didSelectTab(Tab tab, int type, int lastId) {
+                List<Tab> tabList = mTabModelSelector.getTabModelFilterProvider()
+                                            .getCurrentTabModelFilter()
+                                            .getRelatedTabList(lastId);
+                if (tabList.contains(tab)) return;
+                if (isCurrentTabInGroup()) {
+                    if (isTabStripShowing()) {
+                        mUiUpdater.updateTabGroupPopUi();
+                    } else {
+                        maybeShowTabStrip();
+                    }
+                } else {
+                    hideTabStrip();
+                }
+            }
+
+            @Override
+            public void willCloseTab(Tab tab, boolean animate) {
+                if (!isCurrentTabInGroup()) {
+                    hideTabStrip();
+                }
+                if (isTabStripShowing()) {
+                    mUiUpdater.updateTabGroupPopUi();
+                }
+            }
+
+            @Override
+            public void didAddTab(Tab tab, int type, @TabCreationState int creationState) {
+                if (isTabStripShowing()) {
+                    mUiUpdater.updateTabGroupPopUi();
+                    return;
+                }
+                if (type != TabLaunchType.FROM_RESTORE) {
+                    maybeShowTabStrip();
+                }
+            }
+
+            @Override
+            public void tabClosureUndone(Tab tab) {
+                if (isTabStripShowing()) {
+                    mUiUpdater.updateTabGroupPopUi();
+                    return;
+                }
+                maybeShowTabStrip();
+            }
+
+            @Override
+            public void restoreCompleted() {
+                maybeShowTabStrip();
+            }
+        };
+        mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(mTabModelObserver);
+
+        mOverviewModeObserver = new EmptyOverviewModeObserver() {
+            @Override
+            public void onOverviewModeStartedShowing(boolean showToolbar) {
+                mIsOverviewModeVisible = true;
+                hideTabStrip();
+            }
+
+            @Override
+            public void onOverviewModeFinishedHiding() {
+                mIsOverviewModeVisible = false;
+                maybeShowTabStrip();
+            }
+        };
+
+        overviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable((overviewModeBehavior) -> {
+                    mOverviewModeBehavior = overviewModeBehavior;
+                    mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+                }));
+
+        mKeyboardVisibilityListener = new KeyboardVisibilityDelegate.KeyboardVisibilityListener() {
+            private boolean mWasShowingStrip;
+            @Override
+            public void keyboardVisibilityChanged(boolean isShowing) {
+                if (isShowing) {
+                    mWasShowingStrip = isTabStripShowing();
+                    hideTabStrip();
+                } else {
+                    if (mWasShowingStrip) {
+                        maybeShowTabStrip();
+                    }
+                }
+            }
+        };
+        KeyboardVisibilityDelegate.getInstance().addKeyboardVisibilityListener(
+                mKeyboardVisibilityListener);
+
+        mBottomSheetObserver = new EmptyBottomSheetObserver() {
+            private Boolean mWasShowingStrip;
+            @Override
+            public void onSheetStateChanged(int newState) {
+                if (newState == BottomSheetController.SheetState.HIDDEN) {
+                    if (mWasShowingStrip != null && mWasShowingStrip) {
+                        maybeShowTabStrip();
+                    }
+                    mWasShowingStrip = null;
+                } else {
+                    if (mWasShowingStrip == null) {
+                        mWasShowingStrip = isTabStripShowing();
+                    }
+                    hideTabStrip();
+                }
+            }
+        };
+        mBottomSheetController.addObserver(mBottomSheetObserver);
+
+        // TODO(yuezhanggg): Reset the strip with empty tab list as well.
+        mTabGroupUiController.setupLeftButtonOnClickListener(view -> hideTabStrip());
+    }
+
+    void onAnchorViewChanged(View anchorView, int anchorViewId) {
+        boolean isShowing = isTabStripShowing();
+        if (isShowing) {
+            mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        }
+        // When showing bottom toolbar, the arrow on dismiss button should point down; when showing
+        // adaptive toolbar, the arrow on dismiss button should point up.
+        mTabGroupUiController.setupLeftButtonDrawable(anchorViewId == R.id.toolbar
+                        ? R.drawable.ic_expand_less_black_24dp
+                        : R.drawable.ic_expand_more_black_24dp);
+        mModel.set(TabGroupPopupUiProperties.ANCHOR_VIEW, anchorView);
+        if (isShowing) {
+            mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        }
+    }
+
+    void maybeShowTabStrip() {
+        if (mIsOverviewModeVisible || !isCurrentTabInGroup()) return;
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+    }
+
+    private void hideTabStrip() {
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+    }
+
+    private boolean isCurrentTabInGroup() {
+        assert mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter()
+                        instanceof TabGroupModelFilter;
+        TabGroupModelFilter filter =
+                (TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
+                        .getCurrentTabModelFilter();
+        Tab currentTab = mTabModelSelector.getCurrentTab();
+        if (currentTab == null) return false;
+        List<Tab> tabgroup = filter.getRelatedTabList(currentTab.getId());
+        return tabgroup.size() > 1;
+    }
+
+    private boolean isTabStripShowing() {
+        return mModel.get(TabGroupPopupUiProperties.IS_VISIBLE);
+    }
+
+    /**
+     * Destroy any members that needs clean up.
+     */
+    public void destroy() {
+        KeyboardVisibilityDelegate.getInstance().removeKeyboardVisibilityListener(
+                mKeyboardVisibilityListener);
+        if (mOverviewModeBehavior != null) {
+            mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
+        }
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
+        mTabModelSelector.getTabModelFilterProvider().removeTabModelFilterObserver(
+                mTabModelObserver);
+        mBrowserControlsStateProvider.removeObserver(mBrowserControlsObserver);
+        mBottomSheetController.removeObserver(mBottomSheetObserver);
+    }
+
+    @VisibleForTesting
+    boolean getIsOverviewModeVisibleForTesting() {
+        return mIsOverviewModeVisible;
+    }
+}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
index cab57d6..afb4c681 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
@@ -14,10 +14,10 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -113,8 +113,7 @@
     private final SnackbarManager.SnackbarManageable mSnackbarManageable;
     private final Snackbar mUndoClosureSnackBar;
 
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
-    private final Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
+    private CallbackController mCallbackController = new CallbackController();
     private final OverviewModeBehavior.OverviewModeObserver mOverviewModeObserver;
     private OverviewModeBehavior mOverviewModeBehavior;
 
@@ -129,7 +128,7 @@
             BottomControlsCoordinator.BottomControlsVisibilityController visibilityController,
             ResetHandler resetHandler, PropertyModel model, TabModelSelector tabModelSelector,
             TabCreatorManager tabCreatorManager,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ThemeColorProvider themeColorProvider,
             @Nullable TabGridDialogMediator.DialogController dialogController,
             ActivityLifecycleDispatcher activityLifecycleDispatcher,
@@ -139,7 +138,6 @@
         mModel = model;
         mTabModelSelector = tabModelSelector;
         mTabCreatorManager = tabCreatorManager;
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
         mVisibilityController = visibilityController;
         mThemeColorProvider = themeColorProvider;
         mTabGridDialogController = dialogController;
@@ -342,19 +340,11 @@
         mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(mTabModelObserver);
         mTabModelSelector.addObserver(mTabModelSelectorObserver);
 
-        mOverviewModeBehaviorSupplierObserver = new Callback<OverviewModeBehavior>() {
-            @Override
-            public void onResult(OverviewModeBehavior overviewModeBehavior) {
-                assert overviewModeBehavior != null;
-                mOverviewModeBehavior = overviewModeBehavior;
-                mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
-
-                // TODO(crbug.com/1084528): Replace with OneShotSupplier when it is available.
-                mOverviewModeBehaviorSupplier.removeObserver(this);
-                mOverviewModeBehaviorSupplier = null;
-            }
-        };
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        overviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable((overviewModeBehavior) -> {
+                    mOverviewModeBehavior = overviewModeBehavior;
+                    mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+                }));
 
         mThemeColorProvider.addThemeColorObserver(mThemeColorObserver);
         mThemeColorProvider.addTintObserver(mTintObserver);
@@ -530,8 +520,9 @@
         if (mOverviewModeBehavior != null) {
             mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
         }
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
         }
         mThemeColorProvider.removeThemeColorObserver(mThemeColorObserver);
         mThemeColorProvider.removeTintObserver(mTintObserver);
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediatorUnitTest.java
new file mode 100644
index 0000000..209566c
--- /dev/null
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupPopupUiMediatorUnitTest.java
@@ -0,0 +1,601 @@
+// 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.
+
+package org.chromium.chrome.browser.tasks.tab_management;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.supplier.OneshotSupplierImpl;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabCreationState;
+import org.chromium.chrome.browser.tab.TabImpl;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData;
+import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link TabGroupPopupUiMediator}.
+ */
+@SuppressWarnings({"ResultOfMethodCallIgnored", "ArraysAsListWithZeroOrOneArgument"})
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TabGroupPopupUiMediatorUnitTest {
+    @Rule
+    public TestRule mProcessor = new Features.JUnitProcessor();
+
+    private static final String TAB1_TITLE = "Tab1";
+    private static final String TAB2_TITLE = "Tab2";
+    private static final String TAB3_TITLE = "Tab3";
+    private static final String TAB4_TITLE = "Tab4";
+    private static final int TAB1_ID = 456;
+    private static final int TAB2_ID = 789;
+    private static final int TAB3_ID = 123;
+    private static final int TAB4_ID = 357;
+
+    @Mock
+    TabModelSelectorImpl mTabModelSelector;
+    @Mock
+    OverviewModeBehavior mOverviewModeBehavior;
+    @Mock
+    BrowserControlsStateProvider mBrowserControlsStateProvider;
+    @Mock
+    TabGroupPopupUiMediator.TabGroupPopUiUpdater mUpdater;
+    @Mock
+    TabGroupUiMediator.TabGroupUiController mTabGroupUiController;
+    @Mock
+    TabModelFilterProvider mTabModelFilterProvider;
+    @Mock
+    TabGroupModelFilter mTabGroupModelFilter;
+    @Mock
+    KeyboardVisibilityDelegate mKeyboardVisibilityDelegate;
+    @Mock
+    ToolbarPhone mTopAnchorView;
+    @Mock
+    FrameLayout mBottomAnchorView;
+    @Mock
+    BottomSheetController mBottomSheetController;
+    @Captor
+    ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
+    @Captor
+    private ArgumentCaptor<BrowserControlsStateProvider.Observer>
+            mBrowserControlsStateProviderObserverCaptor;
+    @Captor
+    ArgumentCaptor<OverviewModeBehavior.OverviewModeObserver> mOverviewModeObserverCaptor;
+    @Captor
+    ArgumentCaptor<KeyboardVisibilityDelegate.KeyboardVisibilityListener>
+            mKeyboardVisibilityListenerCaptor;
+    @Captor
+    ArgumentCaptor<BottomSheetObserver> mBottomSheetObserver;
+
+    private TabImpl mTab1;
+    private TabImpl mTab2;
+    private TabImpl mTab3;
+    private PropertyModel mModel;
+    private TabGroupPopupUiMediator mMediator;
+    private OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
+            new OneshotSupplierImpl<>();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTab1 = prepareTab(TAB1_ID, TAB1_TITLE);
+        mTab2 = prepareTab(TAB2_ID, TAB2_TITLE);
+        mTab3 = prepareTab(TAB3_ID, TAB3_TITLE);
+
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+        doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
+        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
+        doNothing()
+                .when(mTabModelFilterProvider)
+                .addTabModelFilterObserver(mTabModelObserverCaptor.capture());
+        doNothing()
+                .when(mBrowserControlsStateProvider)
+                .addObserver(mBrowserControlsStateProviderObserverCaptor.capture());
+        doNothing()
+                .when(mOverviewModeBehavior)
+                .addOverviewModeObserver(mOverviewModeObserverCaptor.capture());
+        doNothing()
+                .when(mKeyboardVisibilityDelegate)
+                .addKeyboardVisibilityListener(mKeyboardVisibilityListenerCaptor.capture());
+        doNothing().when(mBottomSheetController).addObserver(mBottomSheetObserver.capture());
+
+        mOverviewModeBehaviorSupplier.set(mOverviewModeBehavior);
+        KeyboardVisibilityDelegate.setInstance(mKeyboardVisibilityDelegate);
+        mModel = new PropertyModel(TabGroupPopupUiProperties.ALL_KEYS);
+        mMediator = new TabGroupPopupUiMediator(mModel, mTabModelSelector,
+                mOverviewModeBehaviorSupplier, mBrowserControlsStateProvider, mUpdater,
+                mTabGroupUiController, mBottomSheetController);
+    }
+
+    @Test
+    public void testOnControlOffsetChanged() {
+        mModel.set(TabGroupPopupUiProperties.CONTENT_VIEW_ALPHA, 0f);
+
+        // Mock that the hidden ratio of browser control is 0.8765.
+        float hiddenRatio = 0.8765f;
+        doReturn(hiddenRatio).when(mBrowserControlsStateProvider).getBrowserControlHiddenRatio();
+        mBrowserControlsStateProviderObserverCaptor.getValue().onControlsOffsetChanged(
+                0, 0, 0, 0, false);
+
+        assertThat(
+                mModel.get(TabGroupPopupUiProperties.CONTENT_VIEW_ALPHA), equalTo(1 - hiddenRatio));
+
+        // Mock that the hidden ratio of browser control is 0.12345.
+        hiddenRatio = 0.1234f;
+        doReturn(hiddenRatio).when(mBrowserControlsStateProvider).getBrowserControlHiddenRatio();
+        mBrowserControlsStateProviderObserverCaptor.getValue().onControlsOffsetChanged(
+                0, 0, 0, 0, false);
+
+        assertThat(
+                mModel.get(TabGroupPopupUiProperties.CONTENT_VIEW_ALPHA), equalTo(1 - hiddenRatio));
+    }
+
+    @Test
+    public void tabSelection_Show() {
+        // Mock that the strip is hidden.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 and tab2 are in the same group, and tab 3 is a single tab.
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID);
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab3)), TAB3_ID);
+
+        doReturn(mTab2).when(mTabModelSelector).getCurrentTab();
+        mTabModelObserverCaptor.getValue().didSelectTab(
+                mTab2, TabLaunchType.FROM_CHROME_UI, TAB3_ID);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabSelection_Hide() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1 and tab2 are in the same group, and tab 3 is a single tab.
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID);
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab3)), TAB3_ID);
+
+        doReturn(mTab3).when(mTabModelSelector).getCurrentTab();
+        mTabModelObserverCaptor.getValue().didSelectTab(
+                mTab3, TabLaunchType.FROM_CHROME_UI, TAB1_ID);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabSelection_Update() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1 and tab2 are in the same group, tab3 and new tab are in the same group.
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID);
+        createTabGroup(
+                new ArrayList<>(Arrays.asList(mTab3, prepareTab(TAB4_ID, TAB4_TITLE))), TAB3_ID);
+
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+        mTabModelObserverCaptor.getValue().didSelectTab(
+                mTab1, TabLaunchType.FROM_CHROME_UI, TAB3_ID);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabSelection_SameGroup() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1 and tab2 are in the same group.
+        createTabGroup(new ArrayList<>(Arrays.asList(mTab1, mTab2)), TAB1_ID);
+
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+        mTabModelObserverCaptor.getValue().didSelectTab(
+                mTab1, TabLaunchType.FROM_CHROME_UI, TAB2_ID);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabClosure_Hide() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1 and tab2 are in the same group, and tab1 is closing.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab2));
+        doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
+        doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
+
+        mTabModelObserverCaptor.getValue().willCloseTab(mTab1, false);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabClosure_Update() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1, tab2 and tab3 are in the same group, and tab1 is closing.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab2, mTab3));
+        doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
+        doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
+        doReturn(tabGroup).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID);
+
+        mTabModelObserverCaptor.getValue().willCloseTab(mTab1, false);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabAddition_Show() {
+        // Mock that the strip is hidden.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 and tab2 are in the same group, and tab2 has just been created.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().didAddTab(
+                mTab2, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabAddition_Update() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1, tab2 and tab3 are in the same group, and tab3 has just been created.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().didAddTab(
+                mTab3, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabAddition_NotShow_Restore() {
+        // Mock that the strip is hidden.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 and tab2 are in the same group, and they are being restored.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().didAddTab(
+                mTab2, TabLaunchType.FROM_RESTORE, TabCreationState.FROZEN_ON_RESTORE);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabClosureUndone_Show() {
+        // Mock that the strip is hiding.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 and tab2 are in the same group, and we have just undone the closure of
+        // tab2.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabClosureUndone_Update() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1, tab2 and tab3 are in the same group, and we have just undone the closure
+        // of tab3.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().tabClosureUndone(mTab3);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+        verify(mUpdater).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabClosureUndone_NotShow() {
+        // Mock that the strip is hiding.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 is a single tab.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1));
+        createTabGroup(tabGroup, TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().tabClosureUndone(mTab1);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        verify(mUpdater, never()).updateTabGroupPopUi();
+    }
+
+    @Test
+    public void tabRestoreCompletion_NotShow() {
+        // Mock that the strip is hiding.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 is the current tab and it is a single tab.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        mTabModelObserverCaptor.getValue().restoreCompleted();
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void tabRestoreCompletion_Show() {
+        // Mock that the strip is hiding.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 is the current tab and it is in a group of {tab1, tab2}.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        mTabModelObserverCaptor.getValue().restoreCompleted();
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+    }
+
+    @Test
+    public void testOverviewModeHiding() {
+        // Mock that the strip is hiding.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 and tab2 are in the same group, and tab1 is the current tab.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        mOverviewModeObserverCaptor.getValue().onOverviewModeFinishedHiding();
+
+        assertThat(mMediator.getIsOverviewModeVisibleForTesting(), equalTo(false));
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+    }
+
+    @Test
+    public void testOverviewModeShowing() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        // Mock that tab1 and tab2 are in the same group, and tab1 is the current tab.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        mOverviewModeObserverCaptor.getValue().onOverviewModeStartedShowing(true);
+
+        assertThat(mMediator.getIsOverviewModeVisibleForTesting(), equalTo(true));
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testNeverShowStripWhenOverviewVisible() {
+        // Mock that the strip is hiding and overview is visible.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        mOverviewModeObserverCaptor.getValue().onOverviewModeStartedShowing(true);
+        assertThat(mMediator.getIsOverviewModeVisibleForTesting(), equalTo(true));
+
+        // Calling maybeShowTabStrip should never show strip in this case.
+        mMediator.maybeShowTabStrip();
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testNeverShowStripWhenSingleTab() {
+        // Mock that the strip is hiding and overview is visible.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        // Mock that tab1 is the current tab and it is a single tab.
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        // Calling maybeShowTabStrip should never show strip in this case.
+        mMediator.maybeShowTabStrip();
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testShowKeyboard_HideStrip() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+
+        mKeyboardVisibilityListenerCaptor.getValue().keyboardVisibilityChanged(true);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testHideKeyboard_ShowStrip() {
+        // Mock that the strip is showing before showing the keyboard. tab1 and tab2 are in the same
+        // group, and tab1 is the current tab.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        // Hide the keyboard after showing it.
+        mKeyboardVisibilityListenerCaptor.getValue().keyboardVisibilityChanged(true);
+        mKeyboardVisibilityListenerCaptor.getValue().keyboardVisibilityChanged(false);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+    }
+
+    @Test
+    public void testHideKeyboard_NotReshowStrip() {
+        // Mock that the strip is hidden before showing the keyboard. tab1 and tab2 are in the same
+        // group, and tab1 is the current tab.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        // Hide the keyboard after showing it.
+        mKeyboardVisibilityListenerCaptor.getValue().keyboardVisibilityChanged(true);
+        mKeyboardVisibilityListenerCaptor.getValue().keyboardVisibilityChanged(false);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testShowBottomSheet_HideStrip() {
+        // Mock that the strip is showing.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+
+        // Show bottom sheet.
+        mBottomSheetObserver.getValue().onSheetStateChanged(BottomSheetController.SheetState.PEEK);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testHideBottomSheet_ShowStrip() {
+        // Mock that the strip is showing before showing the bottom sheet. tab1 and tab2 are in the
+        // same group, and tab1 is the current tab.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        // Hide the bottom sheet after showing it.
+        mBottomSheetObserver.getValue().onSheetStateChanged(BottomSheetController.SheetState.PEEK);
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        mBottomSheetObserver.getValue().onSheetStateChanged(
+                BottomSheetController.SheetState.HIDDEN);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(true));
+    }
+
+    @Test
+    public void testHideBottomSheet_NotReshowStrip() {
+        // Mock that the strip is hidden before showing the bottom sheet. tab1 and tab2 are in the
+        // same group, and tab1 is the current tab.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, false);
+        List<Tab> tabGroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabGroup, TAB1_ID);
+        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+
+        // Hide the bottom sheet after showing it.
+        mBottomSheetObserver.getValue().onSheetStateChanged(BottomSheetController.SheetState.PEEK);
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+        mBottomSheetObserver.getValue().onSheetStateChanged(
+                BottomSheetController.SheetState.HIDDEN);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testAnchorViewChange_TopToolbar() {
+        mMediator.onAnchorViewChanged(mTopAnchorView, R.id.toolbar);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.ANCHOR_VIEW), equalTo(mTopAnchorView));
+        verify(mTabGroupUiController)
+                .setupLeftButtonDrawable(eq(R.drawable.ic_expand_less_black_24dp));
+    }
+
+    @Test
+    public void testAnchorViewChange_BottomToolbar() {
+        mMediator.onAnchorViewChanged(mBottomAnchorView, R.id.bottom_controls);
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.ANCHOR_VIEW), equalTo(mBottomAnchorView));
+        verify(mTabGroupUiController)
+                .setupLeftButtonDrawable(eq(R.drawable.ic_expand_more_black_24dp));
+    }
+
+    @Test
+    public void testAnchorViewChange_WithStripShowing() {
+        // Mock that strip is showing when anchor view changes.
+        mModel.set(TabGroupPopupUiProperties.IS_VISIBLE, true);
+
+        mMediator.onAnchorViewChanged(mBottomAnchorView, R.id.bottom_controls);
+
+        assertTrue(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE));
+        assertThat(mModel.get(TabGroupPopupUiProperties.ANCHOR_VIEW), equalTo(mBottomAnchorView));
+        verify(mTabGroupUiController)
+                .setupLeftButtonDrawable(eq(R.drawable.ic_expand_more_black_24dp));
+    }
+
+    @Test
+    public void testNoCurrentTab_NotShow() {
+        // Mock overview mode is hiding, and current tab is null.
+        doReturn(null).when(mTabModelSelector).getCurrentTab();
+        mOverviewModeObserverCaptor.getValue().onOverviewModeFinishedHiding();
+        assertThat(mMediator.getIsOverviewModeVisibleForTesting(), equalTo(false));
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+
+        mMediator.maybeShowTabStrip();
+
+        assertThat(mModel.get(TabGroupPopupUiProperties.IS_VISIBLE), equalTo(false));
+    }
+
+    @Test
+    public void testDestroy() {
+        mMediator.destroy();
+        verify(mKeyboardVisibilityDelegate)
+                .removeKeyboardVisibilityListener(mKeyboardVisibilityListenerCaptor.capture());
+        verify(mOverviewModeBehavior)
+                .removeOverviewModeObserver(mOverviewModeObserverCaptor.capture());
+        verify(mTabModelFilterProvider)
+                .removeTabModelFilterObserver(mTabModelObserverCaptor.capture());
+        verify(mBrowserControlsStateProvider)
+                .removeObserver(mBrowserControlsStateProviderObserverCaptor.capture());
+    }
+
+    // TODO(yuezhanggg): Pull methods below to a utility class.
+    private TabImpl prepareTab(int id, String title) {
+        TabImpl tab = TabUiUnitTestUtils.prepareTab(id, title, "");
+        doReturn(true).when(tab).isIncognito();
+        return tab;
+    }
+
+    private void createTabGroup(List<Tab> tabs, int rootId) {
+        for (Tab tab : tabs) {
+            when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
+            CriticalPersistedTabData criticalPersistedTabData = CriticalPersistedTabData.from(tab);
+            doReturn(rootId).when(criticalPersistedTabData).getRootId();
+        }
+    }
+}
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
index f9fd908..6b4ba5de 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
@@ -40,7 +40,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
-import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -158,8 +158,8 @@
     private TabGroupUiMediator mTabGroupUiMediator;
     private InOrder mResetHandlerInOrder;
     private InOrder mVisibilityControllerInOrder;
-    private ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
-            new ObservableSupplierImpl<>();
+    private OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
+            new OneshotSupplierImpl<>();
 
     private TabImpl prepareTab(int tabId, int rootId) {
         TabImpl tab = TabUiUnitTestUtils.prepareTab(tabId, rootId);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 5af719f..33b5a90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -42,9 +42,10 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
-import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
@@ -285,8 +286,8 @@
 
     private NextTabPolicySupplier mNextTabPolicySupplier;
 
-    private final ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
-            new ObservableSupplierImpl<>();
+    private final OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
+            new OneshotSupplierImpl<>();
     private OverviewModeController mOverviewModeController;
 
     private ObservableSupplierImpl<EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier =
@@ -848,7 +849,7 @@
         if (!mPendingInitialTabCreation
                 && !(TabUiFeatureUtilities.supportInstantStart(isTablet())
                         && shouldShowTabSwitcherOnStart())) {
-            setInitialOverviewState();
+            setInitialOverviewStateAsync();
         }
 
         if (TabUiFeatureUtilities.isConditionalTabStripEnabled()
@@ -902,7 +903,7 @@
     }
 
     @Override
-    public @Nullable ObservableSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
+    public @Nullable OneshotSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
         return mOverviewModeBehaviorSupplier;
     }
 
@@ -925,7 +926,14 @@
         }
     }
 
-    private void setInitialOverviewState() {
+    // Posts a call to setInitialOverviewStateSync that will trigger after observers of
+    // mOverviewModeBehaviorSupplier have a chance to process the supplied OverviewModeBehavior and
+    // add themselves as observers.
+    private void setInitialOverviewStateAsync() {
+        mOverviewModeBehaviorSupplier.onAvailable((unused) -> setInitalOverviewStateSync());
+    }
+
+    private void setInitalOverviewStateSync() {
         boolean isOverviewVisible = mOverviewModeController.overviewVisible();
 
         if (shouldShowTabSwitcherOnStart() && !isOverviewVisible) {
@@ -1137,7 +1145,7 @@
         if (hasStartWithNativeBeenCalled()
                 && !(TabUiFeatureUtilities.supportInstantStart(isTablet())
                         && shouldShowTabSwitcherOnStart())) {
-            setInitialOverviewState();
+            setInitialOverviewStateAsync();
         }
     }
 
@@ -1521,7 +1529,7 @@
             if (shouldShowTabSwitcherOnStart()) {
                 mLayoutManager.setTabModelSelector(mTabModelSelectorImpl);
                 mIsAccessibilityTabSwitcherEnabled = DeviceClassManager.enableAccessibilityLayout();
-                setInitialOverviewState();
+                setInitialOverviewStateAsync();
             }
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index a72154e..f53ac6e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -50,6 +50,7 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.AppHooks;
@@ -1376,7 +1377,8 @@
      * @return {@link ObservableSupplier} for the {@link OverviewModeBehavior} for this activity
      *         if it supports an overview mode, null otherwise.
      */
-    public @Nullable ObservableSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
+
+    public @Nullable OneshotSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
         return null;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
index 73d85c3..90b20bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -25,10 +25,12 @@
 import androidx.core.graphics.drawable.DrawableCompat;
 
 import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ShortcutHelper;
@@ -83,9 +85,8 @@
     protected final TabModelSelector mTabModelSelector;
     protected final ToolbarManager mToolbarManager;
     protected final View mDecorView;
-    private final @Nullable ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+    private CallbackController mCallbackController = new CallbackController();
     private final ObservableSupplier<BookmarkBridge> mBookmarkBridgeSupplier;
-    private @Nullable Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
     private Callback<BookmarkBridge> mBookmarkBridgeSupplierCallback;
     private boolean mUpdateMenuItemVisible;
     private ShareUtils mShareUtils;
@@ -129,7 +130,7 @@
     public AppMenuPropertiesDelegateImpl(Context context, ActivityTabProvider activityTabProvider,
             MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
             TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView,
-            @Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            @Nullable OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier) {
         mContext = context;
         mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
@@ -139,12 +140,9 @@
         mToolbarManager = toolbarManager;
         mDecorView = decorView;
 
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeSupplierCallback = overviewModeBehavior -> {
-                mOverviewModeBehavior = overviewModeBehavior;
-            };
-            mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
+        if (overviewModeBehaviorSupplier != null) {
+            overviewModeBehaviorSupplier.onAvailable(mCallbackController.makeCancelable(
+                    overviewModeBehavior -> { mOverviewModeBehavior = overviewModeBehavior; }));
         }
 
         mBookmarkBridgeSupplier = bookmarkBridgeSupplier;
@@ -155,10 +153,11 @@
 
     @Override
     public void destroy() {
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
-        }
         mBookmarkBridgeSupplier.removeObserver(mBookmarkBridgeSupplierCallback);
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplier.java
index 3ea752d..c5e1433 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ChromeNextTabPolicySupplier.java
@@ -7,8 +7,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.tabmodel.NextTabPolicy;
 import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
@@ -20,10 +19,8 @@
     private OverviewModeBehavior mOverviewModeBehavior;
 
     public ChromeNextTabPolicySupplier(
-            ObservableSupplier<OverviewModeBehavior> overviewModeControllerObservableSupplier) {
-        // TODO(crbug.com/1084528): Replace this with OneShotSupplier when it is available.
-        new OneShotCallback<>(
-                overviewModeControllerObservableSupplier, this::setOverviewModeBehavior);
+            OneshotSupplier<OverviewModeBehavior> overviewModeControllerObservableSupplier) {
+        overviewModeControllerObservableSupplier.onAvailable(this::setOverviewModeBehavior);
     }
 
     private void setOverviewModeBehavior(@NonNull OverviewModeBehavior overviewModeBehavior) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index f39cfb6..29e0db0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -22,7 +22,8 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeApplication;
@@ -83,8 +84,8 @@
     protected @Nullable WebappActivityCoordinator mWebappActivityCoordinator;
     protected @Nullable TrustedWebActivityCoordinator mTwaCoordinator;
     protected Verifier mVerifier;
-    private ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
-            new ObservableSupplierImpl<>();
+    private OneshotSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
+            new OneshotSupplierImpl<>();
 
     // This is to give the right package name while using the client's resources during an
     // overridePendingTransition call.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 667d3e44..a2b7c8b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.app.ChromeActivity;
@@ -37,7 +38,7 @@
             Supplier<CustomTabActivityNavigationController> customTabNavigationController,
             ActivityTabProvider tabProvider, ObservableSupplier<Profile> profileSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             Supplier<ContextualSearchManager> contextualSearchManagerSupplier) {
         super(activity, null, shareDelegateSupplier, tabProvider, profileSupplier,
                 bookmarkBridgeSupplier, overviewModeBehaviorSupplier,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
index def0a8f..6118eb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
@@ -4,14 +4,11 @@
 
 package org.chromium.chrome.browser.payments;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.components.payments.ComponentPaymentRequestImpl;
 import org.chromium.components.payments.ErrorStrings;
 import org.chromium.components.payments.OriginSecurityChecker;
@@ -20,6 +17,7 @@
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.browser.FeaturePolicyFeature;
 import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.Visibility;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsStatics;
 import org.chromium.mojo.system.MojoException;
@@ -40,7 +38,7 @@
  */
 public class PaymentRequestFactory implements InterfaceFactory<PaymentRequest> {
     // Tests can inject behaviour on future PaymentRequests via these objects.
-    public static PaymentRequestImpl.Delegate sDelegateForTest;
+    public static ComponentPaymentRequestImpl.Delegate sDelegateForTest;
 
     private final RenderFrameHost mRenderFrameHost;
 
@@ -108,8 +106,9 @@
      * Production implementation of the PaymentRequestImpl's Delegate. Gives true answers
      * about the system.
      */
-    public static class PaymentRequestDelegateImpl implements PaymentRequestImpl.Delegate {
-        private final TwaPackageManagerDelegate mPackageManager = new TwaPackageManagerDelegate();
+    public static class PaymentRequestDelegateImpl implements ComponentPaymentRequestImpl.Delegate {
+        private final TwaPackageManagerDelegate mPackageManagerDelegate =
+                new TwaPackageManagerDelegate();
         private final RenderFrameHost mRenderFrameHost;
 
         /* package */ PaymentRequestDelegateImpl(RenderFrameHost renderFrameHost) {
@@ -117,39 +116,42 @@
         }
 
         @Override
-        public boolean isOffTheRecord(WebContents webContents) {
-            // To be conservative, a request which we don't know its profile is considered
-            // off-the-record, and thus user data would not be recorded in this case.
-            ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
-            if (activity == null) return true;
-            TabModel tabModel = activity.getCurrentTabModel();
-            assert tabModel != null;
-            Profile profile = tabModel.getProfile();
+        public boolean isOffTheRecord() {
+            // TODO(crbug.com/1128658): Try getting around the Profile dependency, as in C++ where
+            // we can do web_contents->GetBrowserContext()->IsOffTheRecord().
+            WebContents liveWebContents = getLiveWebContents();
+            if (liveWebContents == null) return true;
+            Profile profile = Profile.fromWebContents(liveWebContents);
             if (profile == null) return true;
             return profile.isOffTheRecord();
         }
 
         @Override
         public String getInvalidSslCertificateErrorMessage() {
-            WebContents webContents = getWebContents();
-            if (webContents == null || webContents.isDestroyed()) return null;
-            if (!OriginSecurityChecker.isSchemeCryptographic(webContents.getLastCommittedUrl())) {
+            WebContents liveWebContents = getLiveWebContents();
+            if (liveWebContents == null) return null;
+            if (!OriginSecurityChecker.isSchemeCryptographic(
+                        liveWebContents.getLastCommittedUrl())) {
                 return null;
             }
-            return SslValidityChecker.getInvalidSslCertificateErrorMessage(webContents);
+            return SslValidityChecker.getInvalidSslCertificateErrorMessage(liveWebContents);
         }
 
         @Override
-        public boolean isWebContentsActive(@NonNull ChromeActivity activity) {
-            return TabModelUtils.getCurrentWebContents(activity.getCurrentTabModel())
-                    == getWebContents();
+        public boolean isWebContentsActive() {
+            // TODO(crbug.com/1128658): Try making the WebContents inactive for instrumentation
+            // tests rather than mocking it with this method.
+            WebContents liveWebContents = getLiveWebContents();
+            return liveWebContents != null && liveWebContents.getVisibility() == Visibility.VISIBLE;
         }
 
         @Override
         public boolean prefsCanMakePayment() {
-            WebContents webContents = getWebContents();
-            return webContents != null && !webContents.isDestroyed()
-                    && UserPrefs.get(Profile.fromWebContents(webContents))
+            // TODO(crbug.com/1128658): Try replacing Profile with BrowserContextHandle, which
+            // represents a Chrome Profile or WebLayer ProfileImpl, and which UserPrefs operates on.
+            WebContents liveWebContents = getLiveWebContents();
+            return liveWebContents != null
+                    && UserPrefs.get(Profile.fromWebContents(liveWebContents))
                                .getBoolean(Pref.CAN_MAKE_PAYMENT_ENABLED);
         }
 
@@ -160,13 +162,17 @@
 
         @Override
         @Nullable
-        public String getTwaPackageName(@Nullable ChromeActivity activity) {
-            return activity != null ? mPackageManager.getTwaPackageName(activity) : null;
+        public String getTwaPackageName() {
+            WebContents liveWebContents = getLiveWebContents();
+            if (liveWebContents == null) return null;
+            ChromeActivity activity = ChromeActivity.fromWebContents(liveWebContents);
+            return activity != null ? mPackageManagerDelegate.getTwaPackageName(activity) : null;
         }
 
         @Nullable
-        private WebContents getWebContents() {
-            return WebContentsStatics.fromRenderFrameHost(mRenderFrameHost);
+        private WebContents getLiveWebContents() {
+            WebContents webContents = WebContentsStatics.fromRenderFrameHost(mRenderFrameHost);
+            return webContents != null && !webContents.isDestroyed() ? webContents : null;
         }
     }
 
@@ -192,7 +198,7 @@
             return new InvalidPaymentRequest();
         }
 
-        PaymentRequestImpl.Delegate delegate;
+        ComponentPaymentRequestImpl.Delegate delegate;
         if (sDelegateForTest != null) {
             delegate = sDelegateForTest;
         } else {
@@ -203,8 +209,8 @@
         if (webContents == null || webContents.isDestroyed()) return new InvalidPaymentRequest();
 
         return ComponentPaymentRequestImpl.createPaymentRequest(mRenderFrameHost,
-                /*isOffTheRecord=*/delegate.isOffTheRecord(webContents),
-                /*skipUiForBasicCard=*/delegate.skipUiForBasicCard(),
+                /*isOffTheRecord=*/delegate.isOffTheRecord(),
+                /*skipUiForBasicCard=*/delegate.skipUiForBasicCard(), delegate,
                 (componentPaymentRequest)
                         -> new PaymentRequestImpl(componentPaymentRequest, delegate));
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index b259d18..6752028 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -8,7 +8,6 @@
 import android.os.Handler;
 import android.text.TextUtils;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
@@ -36,6 +35,7 @@
 import org.chromium.components.payments.CanMakePaymentQuery;
 import org.chromium.components.payments.CheckoutFunnelStep;
 import org.chromium.components.payments.ComponentPaymentRequestImpl;
+import org.chromium.components.payments.ComponentPaymentRequestImpl.Delegate;
 import org.chromium.components.payments.ErrorMessageUtil;
 import org.chromium.components.payments.ErrorStrings;
 import org.chromium.components.payments.Event;
@@ -98,43 +98,6 @@
                    PaymentResponseHelper.PaymentResponseRequesterDelegate,
                    PaymentDetailsConverter.MethodChecker, PaymentUIsManager.Delegate,
                    PaymentUIsObserver {
-    /**
-     * A delegate to ask questions about the system, that allows tests to inject behaviour without
-     * having to modify the entire system. This partially mirrors a similar C++
-     * (Content)PaymentRequestDelegate for the C++ implementation, allowing the test harness to
-     * override behaviour in both in a similar fashion.
-     */
-    public interface Delegate {
-        /**
-         * Returns whether the WebContents is currently showing an off-the-record tab. Return true
-         * if the tab profile is not accessible from the WebContents.
-         */
-        boolean isOffTheRecord(WebContents webContents);
-        /**
-         * Returns a non-null string if there is an invalid SSL certificate on the currently
-         * loaded page.
-         */
-        String getInvalidSslCertificateErrorMessage();
-        /**
-         * Returns true if the web contents that initiated the payment request is active.
-         */
-        boolean isWebContentsActive(@NonNull ChromeActivity activity);
-        /**
-         * Returns whether the preferences allow CAN_MAKE_PAYMENT.
-         */
-        boolean prefsCanMakePayment();
-        /**
-         * Returns true if the UI can be skipped for "basic-card" scenarios. This will only ever
-         * be true in tests.
-         */
-        boolean skipUiForBasicCard();
-        /**
-         * If running inside of a Trusted Web Activity, returns the package name for Trusted Web
-         * Activity. Otherwise returns an empty string or null.
-         */
-        @Nullable
-        String getTwaPackageName(@Nullable ChromeActivity activity);
-    }
 
     private static final String TAG = "PaymentRequest";
     private static boolean sIsLocalCanMakePaymentQueryQuotaEnforcedForTest;
@@ -164,11 +127,13 @@
 
     private final PaymentUIsManager mPaymentUIsManager;
 
-    private PaymentOptions mPaymentOptions;
-    private boolean mRequestShipping;
-    private boolean mRequestPayerName;
-    private boolean mRequestPayerPhone;
-    private boolean mRequestPayerEmail;
+    @Nullable
+    private final PaymentOptions mPaymentOptions;
+    private final boolean mRequestShipping;
+    private final boolean mRequestPayerName;
+    private final boolean mRequestPayerPhone;
+    private final boolean mRequestPayerEmail;
+    private final int mShippingType;
 
     private boolean mIsCanMakePaymentResponsePending;
     private boolean mIsHasEnrolledInstrumentResponsePending;
@@ -178,7 +143,6 @@
     private boolean mHasClosed;
 
     private PaymentRequestSpec mSpec;
-    private int mShippingType;
     private boolean mIsFinishedQueryingPaymentApps;
     private List<PaymentApp> mPendingApps = new ArrayList<>();
     private PaymentApp mInvokedPaymentApp;
@@ -263,6 +227,15 @@
         mWebContents = componentPaymentRequestImpl.getWebContents();
         mMerchantName = mWebContents.getTitle();
         mJourneyLogger = componentPaymentRequestImpl.getJourneyLogger();
+
+        mPaymentOptions = componentPaymentRequestImpl.getPaymentOptions();
+        assert mPaymentOptions != null;
+        mRequestShipping = mPaymentOptions.requestShipping;
+        mRequestPayerName = mPaymentOptions.requestPayerName;
+        mRequestPayerPhone = mPaymentOptions.requestPayerPhone;
+        mRequestPayerEmail = mPaymentOptions.requestPayerEmail;
+        mShippingType = mPaymentOptions.shippingType;
+
         mComponentPaymentRequestImpl = componentPaymentRequestImpl;
         mPaymentUIsManager = new PaymentUIsManager(/*delegate=*/this,
                 /*params=*/this, mWebContents, mIsOffTheRecord, mJourneyLogger, mTopLevelOrigin,
@@ -273,38 +246,10 @@
     // Implement BrowserPaymentRequest:
     @Override
     public boolean initAndValidate(PaymentMethodData[] rawMethodData, PaymentDetails details,
-            @Nullable PaymentOptions options, boolean googlePayBridgeEligible) {
+            boolean googlePayBridgeEligible) {
         assert mComponentPaymentRequestImpl != null;
-        mJourneyLogger.recordCheckoutStep(CheckoutFunnelStep.INITIATED);
-
-        mPaymentOptions = options;
-        mRequestShipping = options != null && options.requestShipping;
-        mRequestPayerName = options != null && options.requestPayerName;
-        mRequestPayerPhone = options != null && options.requestPayerPhone;
-        mRequestPayerEmail = options != null && options.requestPayerEmail;
-        mShippingType = PaymentOptionsUtils.getShippingType(options);
-
-        if (!UrlUtil.isOriginAllowedToUseWebPaymentApis(mWebContents.getLastCommittedUrl())) {
-            Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN);
-            Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
-            mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-            disconnectFromClientWithDebugMessage(ErrorStrings.PROHIBITED_ORIGIN,
-                    PaymentErrorReason.NOT_SUPPORTED_FOR_INVALID_ORIGIN_OR_SSL);
-            return false;
-        }
-
-        mJourneyLogger.setRequestedInformation(
-                mRequestShipping, mRequestPayerEmail, mRequestPayerPhone, mRequestPayerName);
-
-        String rejectShowErrorMessage = mDelegate.getInvalidSslCertificateErrorMessage();
-        if (!TextUtils.isEmpty(rejectShowErrorMessage)) {
-            Log.d(TAG, rejectShowErrorMessage);
-            Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
-            mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER);
-            disconnectFromClientWithDebugMessage(rejectShowErrorMessage,
-                    PaymentErrorReason.NOT_SUPPORTED_FOR_INVALID_ORIGIN_OR_SSL);
-            return false;
-        }
+        assert rawMethodData != null;
+        assert details != null;
 
         boolean googlePayBridgeActivated = googlePayBridgeEligible
                 && SkipToGPayHelper.canActivateExperiment(mWebContents, rawMethodData);
@@ -320,7 +265,7 @@
 
         if (googlePayBridgeActivated) {
             PaymentMethodData data = methodData.get(MethodStrings.GOOGLE_PAY);
-            mSkipToGPayHelper = new SkipToGPayHelper(options, data.gpayBridgeData);
+            mSkipToGPayHelper = new SkipToGPayHelper(mPaymentOptions, data.gpayBridgeData);
         }
 
         mQueryForQuota = new HashMap<>(methodData);
@@ -396,7 +341,7 @@
     /** @return Whether the UI was built. */
     private boolean buildUI(ChromeActivity activity) {
         String error = mPaymentUIsManager.buildPaymentRequestUI(activity,
-                /*isWebContentsActive=*/mDelegate.isWebContentsActive(activity),
+                /*isWebContentsActive=*/mDelegate.isWebContentsActive(),
                 /*waitForUpdatedDetails=*/mWaitForUpdatedDetails);
         if (error != null) {
             mJourneyLogger.setNotShown(NotShownReason.OTHER);
@@ -1129,15 +1074,15 @@
         disconnectFromClientWithDebugMessage(ErrorStrings.USER_CANCELLED);
     }
 
+    private void disconnectFromClientWithDebugMessage(String debugMessage) {
+        disconnectFromClientWithDebugMessage(debugMessage, PaymentErrorReason.USER_CANCEL);
+    }
+
     // Implement BrowserPaymentRequest:
     // This method is not supposed to be used outside this class and
     // ComponentPaymentRequestImpl.
     @Override
-    public void disconnectFromClientWithDebugMessage(String debugMessage) {
-        disconnectFromClientWithDebugMessage(debugMessage, PaymentErrorReason.USER_CANCEL);
-    }
-
-    private void disconnectFromClientWithDebugMessage(String debugMessage, int reason) {
+    public void disconnectFromClientWithDebugMessage(String debugMessage, int reason) {
         Log.d(TAG, debugMessage);
         if (mComponentPaymentRequestImpl != null) {
             mComponentPaymentRequestImpl.onError(reason, debugMessage);
@@ -1441,7 +1386,7 @@
     @Override
     @Nullable
     public String getTwaPackageName() {
-        return mDelegate.getTwaPackageName(ChromeActivity.fromWebContents(mWebContents));
+        return mDelegate.getTwaPackageName();
     }
 
     // PaymentAppFactoryDelegate implementation.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUIsManager.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUIsManager.java
index 303d230f..451639e9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUIsManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUIsManager.java
@@ -575,7 +575,7 @@
                             false /* includeNameInLabel */));
         }
 
-        if (PaymentOptionsUtils.requestShipping(mParams.getPaymentOptions())) {
+        if (mParams.getPaymentOptions().requestShipping) {
             boolean haveCompleteShippingAddress = false;
             for (int i = 0; i < mAutofillProfiles.size(); i++) {
                 if (AutofillAddress.checkAddressCompletionStatus(
@@ -591,9 +591,8 @@
         PaymentOptions options = mParams.getPaymentOptions();
         if (PaymentOptionsUtils.requestAnyContactInformation(mParams.getPaymentOptions())) {
             // Do not persist changes on disk in OffTheRecord mode.
-            mContactEditor = new ContactEditor(PaymentOptionsUtils.requestPayerName(options),
-                    PaymentOptionsUtils.requestPayerPhone(options),
-                    PaymentOptionsUtils.requestPayerEmail(options),
+            mContactEditor = new ContactEditor(options.requestPayerName, options.requestPayerPhone,
+                    options.requestPayerEmail,
                     /*saveToDisk=*/!mIsOffTheRecord);
             boolean haveCompleteContactInfo = false;
             for (int i = 0; i < getAutofillProfiles().size(); i++) {
@@ -885,7 +884,7 @@
 
     /** Implements {@link PaymentRequestUI.Client.shouldShowShippingSection}. */
     public boolean shouldShowShippingSection() {
-        if (!PaymentOptionsUtils.requestShipping(mParams.getPaymentOptions())) return false;
+        if (!mParams.getPaymentOptions().requestShipping) return false;
 
         if (mPaymentMethodsSection == null) return true;
 
@@ -898,16 +897,15 @@
         PaymentApp selectedApp = (mPaymentMethodsSection == null)
                 ? null
                 : (PaymentApp) mPaymentMethodsSection.getSelectedItem();
-        org.chromium.payments.mojom.PaymentOptions options = mParams.getPaymentOptions();
-        if (PaymentOptionsUtils.requestPayerName(options)
-                && (selectedApp == null || !selectedApp.handlesPayerName())) {
+        PaymentOptions options = mParams.getPaymentOptions();
+        if (options.requestPayerName && (selectedApp == null || !selectedApp.handlesPayerName())) {
             return true;
         }
-        if (PaymentOptionsUtils.requestPayerPhone(options)
+        if (options.requestPayerPhone
                 && (selectedApp == null || !selectedApp.handlesPayerPhone())) {
             return true;
         }
-        if (PaymentOptionsUtils.requestPayerEmail(options)
+        if (options.requestPayerEmail
                 && (selectedApp == null || !selectedApp.handlesPayerEmail())) {
             return true;
         }
@@ -1139,8 +1137,7 @@
                 mMerchantSupportsAutofillCards, !PaymentPreferencesUtil.isPaymentCompleteOnce(),
                 mMerchantName, mTopLevelOriginFormattedForDisplay,
                 SecurityStateModel.getSecurityLevelForWebContents(mWebContents),
-                new ShippingStrings(
-                        PaymentOptionsUtils.getShippingType(mParams.getPaymentOptions())),
+                new ShippingStrings(mParams.getPaymentOptions().shippingType),
                 mPaymentUisShowStateReconciler, Profile.fromWebContents(mWebContents));
         activity.getLifecycleDispatcher().register(
                 mPaymentRequestUI); // registered as a PauseResumeWithNativeObserver
@@ -1160,7 +1157,7 @@
                 });
 
         // Add the callback to change the label of shipping addresses depending on the focus.
-        if (PaymentOptionsUtils.requestShipping(mParams.getPaymentOptions())) {
+        if (mParams.getPaymentOptions().requestShipping) {
             setShippingAddressSectionFocusChangedObserverForPaymentRequestUI();
         }
 
@@ -1402,13 +1399,10 @@
         int sectionSize = mPaymentMethodsSection.getSize();
         for (int i = 0; i < sectionSize; i++) {
             PaymentApp app = (PaymentApp) mPaymentMethodsSection.getItem(i);
-            if ((!PaymentOptionsUtils.requestShipping(mParams.getPaymentOptions())
-                        || app.handlesShippingAddress())
-                    && (!PaymentOptionsUtils.requestPayerName(mParams.getPaymentOptions())
-                            || app.handlesPayerName())
-                    && (!PaymentOptionsUtils.requestPayerPhone(mParams.getPaymentOptions())
-                            || app.handlesPayerPhone())
-                    && (!PaymentOptionsUtils.requestPayerEmail(mParams.getPaymentOptions())
+            if ((!mParams.getPaymentOptions().requestShipping || app.handlesShippingAddress())
+                    && (!mParams.getPaymentOptions().requestPayerName || app.handlesPayerName())
+                    && (!mParams.getPaymentOptions().requestPayerPhone || app.handlesPayerPhone())
+                    && (!mParams.getPaymentOptions().requestPayerEmail
                             || app.handlesPayerEmail())) {
                 // There is more than one available app that can provide all merchant requested
                 // information information.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
index 082fcb0..b75c9ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -8,6 +8,7 @@
 import android.view.View;
 
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.app.appmenu.AppMenuIconRowFooter;
@@ -37,7 +38,7 @@
             MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
             TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView,
             AppMenuDelegate appMenuDelegate,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier) {
         super(context, activityTabProvider, multiWindowModeStateDispatcher, tabModelSelector,
                 toolbarManager, decorView, overviewModeBehaviorSupplier, bookmarkBridgeSupplier);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorController.java
index 7917a36..0e1b3848 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorController.java
@@ -16,9 +16,10 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.MathUtils;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -48,10 +49,9 @@
     // May be null if we return from the constructor early. Otherwise will be set.
     private final @Nullable TabModelSelector mTabModelSelector;
     private final @Nullable TabModelSelectorObserver mTabModelSelectorObserver;
-    private @Nullable ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
-    private @Nullable Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
     private @Nullable OverviewModeBehavior mOverviewModeBehavior;
     private @Nullable OverviewModeObserver mOverviewModeObserver;
+    private CallbackController mCallbackController = new CallbackController();
 
     private boolean mUseLightNavigation;
     private boolean mOverviewModeHiding;
@@ -66,7 +66,7 @@
      *         {@link OverviewModeBehavior} associated with the containing activity.
      */
     TabbedNavigationBarColorController(Window window, TabModelSelector tabModelSelector,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
         assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
 
         mWindow = window;
@@ -93,9 +93,8 @@
         };
         mTabModelSelector.addObserver(mTabModelSelectorObserver);
 
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeSupplierCallback = this::setOverviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
+        overviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable(this::setOverviewModeBehavior));
 
         // TODO(https://crbug.com/806054): Observe tab loads to restrict black bottom nav to
         // incognito NTP.
@@ -110,12 +109,13 @@
      */
     public void destroy() {
         if (mTabModelSelector != null) mTabModelSelector.removeObserver(mTabModelSelectorObserver);
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
-        }
         if (mOverviewModeBehavior != null) {
             mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
         }
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
         VrModuleProvider.unregisterVrModeObserver(this);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 60c7f801..e484362 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -14,6 +14,7 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -101,7 +102,7 @@
             ObservableSupplierImpl<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
             ObservableSupplier<Profile> profileSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             Supplier<ContextualSearchManager> contextualSearchManagerSupplier) {
         super(activity, onOmniboxFocusChangedListener, shareDelegateSupplier, tabProvider,
                 profileSupplier, bookmarkBridgeSupplier, overviewModeBehaviorSupplier,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedSystemUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedSystemUiCoordinator.java
index 4c0674f..69ae2ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedSystemUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedSystemUiCoordinator.java
@@ -10,6 +10,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 
@@ -32,7 +33,7 @@
      *         {@link OverviewModeBehavior} associated with the containing activity.
      */
     public TabbedSystemUiCoordinator(Window window, TabModelSelector tabModelSelector,
-            @Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
+            @Nullable OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
             assert overviewModeBehaviorSupplier != null;
             mNavigationBarColorController = new TabbedNavigationBarColorController(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index c31f64c..6c71f07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -21,8 +21,10 @@
 import androidx.appcompat.app.ActionBar;
 
 import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -142,8 +144,8 @@
     private OverviewModeObserver mOverviewModeObserver;
 
     private OverviewModeBehavior mOverviewModeBehavior;
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+    private OneshotSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+    private CallbackController mCallbackController = new CallbackController();
 
     private SceneChangeObserver mSceneChangeObserver;
     private final ActionBarDelegate mActionBarDelegate;
@@ -219,7 +221,7 @@
             FindToolbarManager findToolbarManager, ObservableSupplier<Profile> profileSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
             @Nullable Supplier<Boolean> canAnimateNativeBrowserControls,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ObservableSupplier<AppMenuCoordinator> appMenuCoordinatorSupplier,
             boolean shouldShowUpdateBadge) {
         TraceEvent.begin("ToolbarManager.ToolbarManager");
@@ -248,8 +250,8 @@
         mProfileSupplier.addObserver(mProfileSupplierObserver);
 
         mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeBehaviorSupplierObserver = this::setOverviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        mOverviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable(this::setOverviewModeBehavior));
 
         mComponentCallbacks = new ComponentCallbacks() {
             @Override
@@ -823,9 +825,7 @@
         }
 
         if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
             mOverviewModeBehaviorSupplier = null;
-            mOverviewModeBehaviorSupplierObserver = null;
         }
 
         if (mLayoutManager != null) {
@@ -890,6 +890,11 @@
             mMenuButtonCoordinator = null;
         }
 
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
+
         mActivity.unregisterComponentCallbacks(mComponentCallbacks);
         mComponentCallbacks = null;
         ChromeAccessibilityUtil.get().removeObserver(this);
@@ -1233,13 +1238,8 @@
     }
 
     private void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
-        assert overviewModeBehavior != null;
-        assert mOverviewModeBehavior
-                == null
-            : "TODO(https://crbug.com/1084528): the overview mode manager should set at most once.";
         mOverviewModeBehavior = overviewModeBehavior;
         mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
-
         mAppThemeColorProvider.setOverviewModeBehavior(mOverviewModeBehavior);
         mLocationBarModel.setOverviewModeBehavior(mOverviewModeBehavior);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
index 0613149..91aa315 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -14,6 +14,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -85,7 +86,7 @@
             ObservableSupplier<AppMenuButtonHelper> menuButtonHelperSupplier,
             Supplier<Boolean> showStartSurfaceCallable, Runnable openHomepageAction,
             Callback<Integer> setUrlBarFocusAction,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ScrimCoordinator scrimCoordinator) {
         final ScrollingBottomViewResourceFrameLayout root =
                 (ScrollingBottomViewResourceFrameLayout) stub.inflate();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
new file mode 100644
index 0000000..f4be501
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
@@ -0,0 +1,279 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+
+import org.chromium.base.Callback;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.omnibox.LocationBar;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
+import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
+import org.chromium.components.feature_engagement.EventConstants;
+import org.chromium.components.feature_engagement.Tracker;
+
+/**
+ * The root coordinator for the bottom toolbar. It has two sub-components: the browsing mode bottom
+ * toolbar and the tab switcher mode bottom toolbar.
+ */
+class BottomToolbarCoordinator {
+    /** The browsing mode bottom toolbar component */
+    private final BrowsingModeBottomToolbarCoordinator mBrowsingModeCoordinator;
+
+    /** The tab switcher mode bottom toolbar component */
+    private TabSwitcherBottomToolbarCoordinator mTabSwitcherModeCoordinator;
+
+    /** The tab switcher mode bottom toolbar stub that will be inflated when native is ready. */
+    private final ViewStub mTabSwitcherModeStub;
+
+    /** A provider that notifies components when the theme color changes.*/
+    private final ThemeColorProvider mThemeColorProvider;
+
+    /** The overview mode manager. */
+    private OverviewModeBehavior mOverviewModeBehavior;
+    private OverviewModeObserver mOverviewModeObserver;
+
+    /** The activity tab provider. */
+    private ActivityTabProvider mTabProvider;
+
+    private final ObservableSupplier<ShareDelegate> mShareDelegateSupplier;
+    private final Callback<ShareDelegate> mShareDelegateSupplierCallback;
+    private ObservableSupplierImpl<OnClickListener> mShareButtonListenerSupplier =
+            new ObservableSupplierImpl<>();
+    private final Supplier<Boolean> mShowStartSurfaceCallable;
+    private AppMenuButtonHelper mMenuButtonHelper;
+    private OneshotSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+
+    /**
+     * Build the coordinator that manages the bottom toolbar.
+     * @param stub The bottom toolbar {@link ViewStub} to inflate.
+     * @param tabProvider The {@link ActivityTabProvider} used for making the IPH.
+     * @param themeColorProvider The {@link ThemeColorProvider} for the bottom toolbar.
+     * @param shareDelegateSupplier The supplier for the {@link ShareDelegate} the bottom controls
+     *         should use to share content.
+     * @param showStartSurfaceCallable The action that opens the start surface, returning true if
+     * the start surface is shown.
+     * @param openHomepageAction The action that opens the homepage.
+     * @param setUrlBarFocusAction The function that sets Url bar focus. The first argument is
+     * @param overviewModeBehaviorSupplier Supplier for the overview mode manager.
+     * @param menuButtonHelperSupplier
+     */
+    BottomToolbarCoordinator(ViewStub stub, ActivityTabProvider tabProvider,
+            OnLongClickListener tabsSwitcherLongClickListner, ThemeColorProvider themeColorProvider,
+            ObservableSupplier<ShareDelegate> shareDelegateSupplier,
+            Supplier<Boolean> showStartSurfaceCallable, Runnable openHomepageAction,
+            Callback<Integer> setUrlBarFocusAction,
+            ObservableSupplier<AppMenuButtonHelper> menuButtonHelperSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
+        View root = stub.inflate();
+
+        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
+
+        mShowStartSurfaceCallable = showStartSurfaceCallable;
+        final OnClickListener homeButtonListener = v -> {
+            recordBottomToolbarUseForIPH();
+            openHomepageAction.run();
+        };
+
+        final OnClickListener searchAcceleratorListener = v -> {
+            recordBottomToolbarUseForIPH();
+            RecordUserAction.record("MobileToolbarOmniboxAcceleratorTap");
+
+            // Only switch to HomePage when overview is showing.
+            if (mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible()) {
+                mShowStartSurfaceCallable.get();
+            }
+            setUrlBarFocusAction.onResult(LocationBar.OmniboxFocusReason.ACCELERATOR_TAP);
+        };
+
+        mBrowsingModeCoordinator = new BrowsingModeBottomToolbarCoordinator(root, tabProvider,
+                homeButtonListener, searchAcceleratorListener, mShareButtonListenerSupplier,
+                tabsSwitcherLongClickListner, mOverviewModeBehaviorSupplier);
+
+        mTabSwitcherModeStub = root.findViewById(R.id.bottom_toolbar_tab_switcher_mode_stub);
+
+        mThemeColorProvider = themeColorProvider;
+        mTabProvider = tabProvider;
+
+        mShareDelegateSupplier = shareDelegateSupplier;
+        mShareDelegateSupplierCallback = this::onShareDelegateAvailable;
+        mShareDelegateSupplier.addObserver(mShareDelegateSupplierCallback);
+
+        new OneShotCallback<>(menuButtonHelperSupplier, (menuButtonHelper) -> {
+            if (menuButtonHelper != null) {
+                mMenuButtonHelper = menuButtonHelper;
+                mMenuButtonHelper.setOnClickRunnable(() -> recordBottomToolbarUseForIPH());
+            }
+        });
+    }
+
+    /**
+     * Initialize the bottom toolbar with the components that had native initialization
+     * dependencies.
+     * <p>
+     * Calling this must occur after the native library have completely loaded.
+     * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
+     *                            tab switcher button is clicked.
+     * @param newTabClickListener An {@link OnClickListener} that is triggered when the
+     *                            new tab button is clicked.
+     * @param tabCountProvider Updates the tab count number in the tab switcher button and in the
+     *                         incognito toggle tab layout.
+     * @param incognitoStateProvider Notifies components when incognito mode is entered or exited.
+     * @param topToolbarRoot The root {@link ViewGroup} of the top toolbar.
+     * @param closeAllTabsAction The runnable that closes all tabs in the current tab model.
+     */
+    void initializeWithNative(OnClickListener tabSwitcherListener,
+            OnClickListener newTabClickListener, TabCountProvider tabCountProvider,
+            IncognitoStateProvider incognitoStateProvider, ViewGroup topToolbarRoot,
+            Runnable closeAllTabsAction) {
+        final OnClickListener closeTabsClickListener = v -> {
+            recordBottomToolbarUseForIPH();
+            final boolean isIncognito = incognitoStateProvider.isIncognitoSelected();
+            if (isIncognito) {
+                RecordUserAction.record("MobileToolbarCloseAllIncognitoTabsButtonTap");
+            } else {
+                RecordUserAction.record("MobileToolbarCloseAllRegularTabsButtonTap");
+            }
+
+            closeAllTabsAction.run();
+        };
+
+        newTabClickListener = wrapBottomToolbarClickListenerForIPH(newTabClickListener);
+        tabSwitcherListener = wrapBottomToolbarClickListenerForIPH(tabSwitcherListener);
+        mBrowsingModeCoordinator.initializeWithNative(newTabClickListener, tabSwitcherListener,
+                mMenuButtonHelper, tabCountProvider, mThemeColorProvider, incognitoStateProvider);
+        mTabSwitcherModeCoordinator = new TabSwitcherBottomToolbarCoordinator(mTabSwitcherModeStub,
+                topToolbarRoot, incognitoStateProvider, mThemeColorProvider, newTabClickListener,
+                closeTabsClickListener, mMenuButtonHelper, tabCountProvider);
+
+        // Do not change bottom bar if StartSurface Single Pane is enabled and HomePage is not
+        // customized.
+        if (!ReturnToChromeExperimentsUtil.shouldShowStartSurfaceAsTheHomePage()
+                && BottomToolbarVariationManager.shouldBottomToolbarBeVisibleInOverviewMode()) {
+            mOverviewModeObserver = new EmptyOverviewModeObserver() {
+                @Override
+                public void onOverviewModeStartedShowing(boolean showToolbar) {
+                    mBrowsingModeCoordinator.getSearchAccelerator().setEnabled(false);
+                    if (BottomToolbarVariationManager.isShareButtonOnBottom()) {
+                        mBrowsingModeCoordinator.getShareButton().setEnabled(false);
+                    }
+                    if (BottomToolbarVariationManager.isHomeButtonOnBottom()) {
+                        mBrowsingModeCoordinator.getHomeButton().setEnabled(false);
+                    }
+                }
+
+                @Override
+                public void onOverviewModeStartedHiding(
+                        boolean showToolbar, boolean delayAnimation) {
+                    mBrowsingModeCoordinator.getSearchAccelerator().setEnabled(true);
+                    if (BottomToolbarVariationManager.isShareButtonOnBottom()) {
+                        mBrowsingModeCoordinator.getShareButton().updateButtonEnabledState(
+                                mTabProvider.get());
+                    }
+                    if (BottomToolbarVariationManager.isHomeButtonOnBottom()) {
+                        mBrowsingModeCoordinator.getHomeButton().updateButtonEnabledState(
+                                mTabProvider.get());
+                    }
+                }
+            };
+            mOverviewModeBehaviorSupplier.onAvailable(this::setOverviewModeBehavior);
+        }
+    }
+
+    /**
+     * @param isVisible Whether the bottom toolbar is visible.
+     */
+    void setBottomToolbarVisible(boolean isVisible) {
+        if (mTabSwitcherModeCoordinator != null) {
+            mTabSwitcherModeCoordinator.showToolbarOnTop(!isVisible);
+        }
+        mBrowsingModeCoordinator.onVisibilityChanged(isVisible);
+    }
+
+    /**
+     * Clean up any state when the bottom toolbar is destroyed.
+     */
+    void destroy() {
+        mBrowsingModeCoordinator.destroy();
+        if (mTabSwitcherModeCoordinator != null) {
+            mTabSwitcherModeCoordinator.destroy();
+            mTabSwitcherModeCoordinator = null;
+        }
+        if (mOverviewModeBehavior != null) {
+            mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
+            mOverviewModeBehavior = null;
+        }
+        if (mOverviewModeBehaviorSupplier != null) {
+            mOverviewModeBehaviorSupplier = null;
+        }
+        mThemeColorProvider.destroy();
+        mShareDelegateSupplier.removeObserver(mShareDelegateSupplierCallback);
+    }
+
+    private void onShareDelegateAvailable(ShareDelegate shareDelegate) {
+        final OnClickListener shareButtonListener = v -> {
+            if (BottomToolbarVariationManager.isShareButtonOnBottom()) {
+                recordBottomToolbarUseForIPH();
+                RecordUserAction.record("MobileBottomToolbarShareButton");
+            }
+
+            Tab tab = mTabProvider.get();
+            shareDelegate.share(tab, /*shareDirectly=*/false);
+        };
+
+        mShareButtonListenerSupplier.set(shareButtonListener);
+    }
+
+    private void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
+        assert overviewModeBehavior != null;
+        assert mOverviewModeBehavior
+                == null
+            : "TODO(https://crbug.com/1084528): the overview mode manager should set at most once.";
+        mOverviewModeBehavior = overviewModeBehavior;
+        mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+    }
+
+    /** Record that the bottom toolbar was used for IPH reasons. */
+    private void recordBottomToolbarUseForIPH() {
+        Tab tab = mTabProvider.get();
+        if (tab == null) return;
+
+        Tracker tracker =
+                TrackerFactory.getTrackerForProfile(Profile.fromWebContents(tab.getWebContents()));
+        tracker.notifyEvent(EventConstants.CHROME_DUET_USED_BOTTOM_TOOLBAR);
+    }
+
+    /**
+     * Add bottom toolbar IPH tracking to an existing click listener.
+     * @param listener The listener to add bottom toolbar tracking to.
+     */
+    private OnClickListener wrapBottomToolbarClickListenerForIPH(OnClickListener listener) {
+        return (v) -> {
+            recordBottomToolbarUseForIPH();
+            listener.onClick(v);
+        };
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
new file mode 100644
index 0000000..3b5c982
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
@@ -0,0 +1,251 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabUtils;
+import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
+import org.chromium.chrome.browser.toolbar.HomeButton;
+import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
+import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.TabSwitcherButtonView;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+/**
+ * The coordinator for the browsing mode bottom toolbar. This class has two primary components,
+ * an Android view that handles user actions and a composited texture that draws when the controls
+ * are being scrolled off-screen. The Android version does not draw unless the controls offset is 0.
+ */
+public class BrowsingModeBottomToolbarCoordinator {
+    /** The mediator that handles events from outside the browsing mode bottom toolbar. */
+    private final BrowsingModeBottomToolbarMediator mMediator;
+
+    /** The home button that lives in the bottom toolbar. */
+    private final HomeButton mHomeButton;
+
+    /** The share button that lives in the bottom toolbar. */
+    private final ShareButton mShareButton;
+
+    /** The new tab button that lives in the bottom toolbar. */
+    private final BottomToolbarNewTabButton mNewTabButton;
+
+    /** The search accelerator that lives in the bottom toolbar. */
+    private final SearchAccelerator mSearchAccelerator;
+
+    /** The tab switcher button component that lives in the bottom toolbar. */
+    private final TabSwitcherButtonCoordinator mTabSwitcherButtonCoordinator;
+
+    /** The tab switcher button view that lives in the bottom toolbar. */
+    private final TabSwitcherButtonView mTabSwitcherButtonView;
+
+    /** The view group that includes all views shown on browsing mode */
+    private final BrowsingModeBottomToolbarLinearLayout mToolbarRoot;
+
+    /** The model for the browsing mode bottom toolbar that holds all of its state. */
+    private final BrowsingModeBottomToolbarModel mModel;
+
+    /** The callback to be exectured when the share button on click listener is available. */
+    private Callback<OnClickListener> mShareButtonListenerSupplierCallback;
+
+    /** The supplier for the share button on click listener. */
+    private ObservableSupplier<OnClickListener> mShareButtonListenerSupplier;
+
+    /** The activity tab provider that used for making the IPH. */
+    private final ActivityTabProvider mTabProvider;
+
+    /**
+     * Build the coordinator that manages the browsing mode bottom toolbar.
+     * @param root The root {@link View} for locating the views to inflate.
+     * @param tabProvider The {@link ActivityTabProvider} used for making the IPH.
+     * @param homeButtonListener The {@link OnClickListener} for the home button.
+     * @param searchAcceleratorListener The {@link OnClickListener} for the search accelerator.
+     * @param shareButtonListener The {@link OnClickListener} for the share button.
+     * @param overviewModeBehaviorSupplier Supplier for the overview mode manager.
+     */
+    BrowsingModeBottomToolbarCoordinator(View root, ActivityTabProvider tabProvider,
+            OnClickListener homeButtonListener, OnClickListener searchAcceleratorListener,
+            ObservableSupplier<OnClickListener> shareButtonListenerSupplier,
+            OnLongClickListener tabSwitcherLongClickListener,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
+        mModel = new BrowsingModeBottomToolbarModel();
+        mToolbarRoot = root.findViewById(R.id.bottom_toolbar_browsing);
+        mTabProvider = tabProvider;
+
+        PropertyModelChangeProcessor.create(
+                mModel, mToolbarRoot, new BrowsingModeBottomToolbarViewBinder());
+
+        mMediator = new BrowsingModeBottomToolbarMediator(mModel);
+
+        mHomeButton = mToolbarRoot.findViewById(R.id.bottom_home_button);
+        mHomeButton.setOnClickListener(homeButtonListener);
+        mHomeButton.setActivityTabProvider(mTabProvider);
+
+        mNewTabButton = mToolbarRoot.findViewById(R.id.bottom_new_tab_button);
+
+        mShareButton = mToolbarRoot.findViewById(R.id.bottom_share_button);
+
+        mSearchAccelerator = mToolbarRoot.findViewById(R.id.search_accelerator);
+        mSearchAccelerator.setOnClickListener(searchAcceleratorListener);
+
+        // TODO(amaralp): Make this adhere to MVC framework.
+        mTabSwitcherButtonView = mToolbarRoot.findViewById(R.id.bottom_tab_switcher_button);
+        mTabSwitcherButtonCoordinator = new TabSwitcherButtonCoordinator(mTabSwitcherButtonView);
+
+        mTabSwitcherButtonView.setOnLongClickListener(tabSwitcherLongClickListener);
+        if (BottomToolbarVariationManager.isNewTabButtonOnBottom()) {
+            mNewTabButton.setVisibility(View.VISIBLE);
+        }
+        if (BottomToolbarVariationManager.isHomeButtonOnBottom()) {
+            mHomeButton.setVisibility(View.VISIBLE);
+        }
+
+        if (BottomToolbarVariationManager.isTabSwitcherOnBottom()) {
+            mTabSwitcherButtonView.setVisibility(View.VISIBLE);
+        }
+        if (BottomToolbarVariationManager.isShareButtonOnBottom()) {
+            mShareButton.setVisibility(View.VISIBLE);
+            mShareButtonListenerSupplierCallback = shareButtonListener -> {
+                mShareButton.setOnClickListener(shareButtonListener);
+            };
+            mShareButtonListenerSupplier = shareButtonListenerSupplier;
+            mShareButton.setActivityTabProvider(mTabProvider);
+            mShareButtonListenerSupplier.addObserver(mShareButtonListenerSupplierCallback);
+        }
+
+        overviewModeBehaviorSupplier.onAvailable(this::setOverviewModeBehavior);
+    }
+
+    /**
+     * @param isVisible Whether the bottom toolbar is visible.
+     */
+    void onVisibilityChanged(boolean isVisible) {
+        if (isVisible) return;
+        Tab tab = mTabProvider.get();
+        if (tab != null) mMediator.dismissIPH(TabUtils.getActivity(tab));
+    }
+
+    /**
+     * Initialize the bottom toolbar with the components that had native initialization
+     * dependencies.
+     * <p>
+     * Calling this must occur after the native library have completely loaded.
+     * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
+     *                            tab switcher button is clicked.
+     * @param menuButtonHelper An {@link AppMenuButtonHelper} that is triggered when the
+     *                         menu button is clicked.
+     * @param tabCountProvider Updates the tab count number in the tab switcher button.
+     * @param themeColorProvider Notifies components when theme color changes.
+     * @param incognitoStateProvider Notifies components when incognito state changes.
+     */
+    void initializeWithNative(OnClickListener newTabListener, OnClickListener tabSwitcherListener,
+            AppMenuButtonHelper menuButtonHelper, TabCountProvider tabCountProvider,
+            ThemeColorProvider themeColorProvider, IncognitoStateProvider incognitoStateProvider) {
+        mMediator.setThemeColorProvider(themeColorProvider);
+        if (BottomToolbarVariationManager.isNewTabButtonOnBottom()) {
+            mNewTabButton.setOnClickListener(newTabListener);
+            mNewTabButton.setThemeColorProvider(themeColorProvider);
+            mNewTabButton.setIncognitoStateProvider(incognitoStateProvider);
+        }
+        if (BottomToolbarVariationManager.isHomeButtonOnBottom()) {
+            mHomeButton.setThemeColorProvider(themeColorProvider);
+        }
+
+        if (BottomToolbarVariationManager.isShareButtonOnBottom()) {
+            mShareButton.setThemeColorProvider(themeColorProvider);
+        }
+
+        mSearchAccelerator.setThemeColorProvider(themeColorProvider);
+        mSearchAccelerator.setIncognitoStateProvider(incognitoStateProvider);
+
+        if (BottomToolbarVariationManager.isTabSwitcherOnBottom()) {
+            mTabSwitcherButtonCoordinator.setTabSwitcherListener(tabSwitcherListener);
+            mTabSwitcherButtonCoordinator.setThemeColorProvider(themeColorProvider);
+            mTabSwitcherButtonCoordinator.setTabCountProvider(tabCountProvider);
+        }
+    }
+
+    private void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
+        assert overviewModeBehavior != null;
+
+        // If StartSurface is HomePage, BrowsingModeBottomToolbar is shown in browsing mode and in
+        // overview mode. We need to pass the OverviewModeBehavior to the buttons so they are
+        // disabled based on the overview state.
+        if (ReturnToChromeExperimentsUtil.shouldShowStartSurfaceAsTheHomePage()) {
+            mShareButton.setOverviewModeBehavior(overviewModeBehavior);
+            mTabSwitcherButtonCoordinator.setOverviewModeBehavior(overviewModeBehavior);
+            mHomeButton.setOverviewModeBehavior(overviewModeBehavior);
+        }
+    }
+
+    /**
+     * @param enabled Whether to disable click events on the bottom toolbar. Setting true can also
+     *                prevent from all click events on toolbar and all children views on toolbar.
+     */
+    void setTouchEnabled(boolean enabled) {
+        mToolbarRoot.setTouchEnabled(enabled);
+    }
+
+    /**
+     * @param visible Whether to hide the tab switcher bottom toolbar
+     */
+    void setVisible(boolean visible) {
+        mModel.set(BrowsingModeBottomToolbarModel.IS_VISIBLE, visible);
+    }
+
+    /**
+     * @return The browsing mode bottom toolbar's share button.
+     */
+    ShareButton getShareButton() {
+        return mShareButton;
+    }
+
+    /**
+     * @return The browsing mode bottom toolbar's tab switcher button.
+     */
+    TabSwitcherButtonView getTabSwitcherButtonView() {
+        return mTabSwitcherButtonView;
+    }
+
+    /**
+     * @return The browsing mode bottom toolbar's search button.
+     */
+    SearchAccelerator getSearchAccelerator() {
+        return mSearchAccelerator;
+    }
+
+    /**
+     * @return The browsing mode bottom toolbar's home button.
+     */
+    HomeButton getHomeButton() {
+        return mHomeButton;
+    }
+
+    /**
+     * Clean up any state when the browsing mode bottom toolbar is destroyed.
+     */
+    public void destroy() {
+        if (mShareButtonListenerSupplier != null) {
+            mShareButtonListenerSupplier.removeObserver(mShareButtonListenerSupplierCallback);
+        }
+        mMediator.destroy();
+        mHomeButton.destroy();
+        mShareButton.destroy();
+        mSearchAccelerator.destroy();
+        mTabSwitcherButtonCoordinator.destroy();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
index dd551dbeba..bc76e1d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java
@@ -11,9 +11,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -49,19 +49,17 @@
     private ThemeColorProvider mThemeColorProvider;
     private OnClickListener mTabSwitcherClickListener;
     private OnLongClickListener mTabSwitcherLongClickListener;
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
     private MenuButtonCoordinator mMenuButtonCoordinator;
+    private CallbackController mCallbackController = new CallbackController();
 
     StartSurfaceToolbarCoordinator(ViewStub startSurfaceToolbarStub,
             IdentityDiscController identityDiscController, UserEducationHelper userEducationHelper,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ThemeColorProvider provider, MenuButtonCoordinator menuButtonCoordinator) {
         mStub = startSurfaceToolbarStub;
 
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeBehaviorSupplierObserver = this::setOverviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        overviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable(this::setOverviewModeBehavior));
 
         mPropertyModel =
                 new PropertyModel.Builder(StartSurfaceToolbarProperties.ALL_KEYS)
@@ -100,11 +98,6 @@
      * Cleans up any code and removes observers as necessary.
      */
     void destroy() {
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
-            mOverviewModeBehaviorSupplier = null;
-            mOverviewModeBehaviorSupplierObserver = null;
-        }
         mToolbarMediator.destroy();
         if (mIncognitoSwitchCoordinator != null) mIncognitoSwitchCoordinator.destroy();
         if (mTabSwitcherButtonCoordinator != null) mTabSwitcherButtonCoordinator.destroy();
@@ -112,6 +105,10 @@
             mMenuButtonCoordinator.destroy();
             mMenuButtonCoordinator = null;
         }
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
         mTabSwitcherButtonCoordinator = null;
         mTabSwitcherButtonView = null;
         mTabCountProvider = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
index fe28253..75b38ce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
@@ -15,9 +15,10 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.Invalidator;
@@ -78,10 +79,9 @@
     private final IdentityDiscController mIdentityDiscController;
     private OptionalBrowsingModeButtonController mOptionalButtonController;
 
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
     private MenuButtonCoordinator mMenuButtonCoordinator;
     private ObservableSupplier<AppMenuButtonHelper> mAppMenuButtonHelperSupplier;
+    private CallbackController mCallbackController = new CallbackController();
 
     private HomepageManager.HomepageStateListener mHomepageStateListener =
             new HomepageManager.HomepageStateListener() {
@@ -108,7 +108,7 @@
             ToolbarLayout toolbarLayout, IdentityDiscController identityDiscController,
             ToolbarDataProvider toolbarDataProvider, ToolbarTabController tabController,
             UserEducationHelper userEducationHelper, List<ButtonDataProvider> buttonDataProviders,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             ThemeColorProvider normalThemeColorProvider,
             ThemeColorProvider overviewThemeColorProvider,
             MenuButtonCoordinator browsingModeMenuButtonCoordinator,
@@ -120,15 +120,14 @@
         mOptionalButtonController = new OptionalBrowsingModeButtonController(buttonDataProviders,
                 userEducationHelper, mToolbarLayout, () -> toolbarDataProvider.getTab());
 
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeBehaviorSupplierObserver = this::setOverviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        overviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable(this::setOverviewModeBehavior));
 
         if (mToolbarLayout instanceof ToolbarPhone) {
             if (StartSurfaceConfiguration.isStartSurfaceEnabled()) {
                 mStartSurfaceToolbarCoordinator = new StartSurfaceToolbarCoordinator(
                         controlContainer.getRootView().findViewById(R.id.tab_switcher_toolbar_stub),
-                        mIdentityDiscController, userEducationHelper, mOverviewModeBehaviorSupplier,
+                        mIdentityDiscController, userEducationHelper, overviewModeBehaviorSupplier,
                         overviewThemeColorProvider, startSurfaceMenuButtonCoordinator);
             } else {
                 mTabSwitcherModeCoordinatorPhone = new TabSwitcherModeTTCoordinatorPhone(
@@ -232,11 +231,6 @@
      */
     public void destroy() {
         HomepageManager.getInstance().removeListener(mHomepageStateListener);
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
-            mOverviewModeBehaviorSupplier = null;
-            mOverviewModeBehaviorSupplierObserver = null;
-        }
         mToolbarLayout.destroy();
         if (mTabSwitcherModeCoordinatorPhone != null) {
             mTabSwitcherModeCoordinatorPhone.destroy();
@@ -244,6 +238,11 @@
             mStartSurfaceToolbarCoordinator.destroy();
         }
 
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
+
         if (mOptionalButtonController != null) {
             mOptionalButtonController.destroy();
             mOptionalButtonController = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index fbecdc2..fbe4118 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -21,6 +21,7 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -114,8 +115,7 @@
     private OverlayPanelManager.OverlayPanelManagerObserver mOverlayPanelManagerObserver;
 
     private OverviewModeBehavior mOverviewModeBehavior;
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+    private OneshotSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
     private OverviewModeBehavior.OverviewModeObserver mOverviewModeObserver;
 
     /** A means of providing the theme color to different features. */
@@ -168,7 +168,7 @@
             ObservableSupplier<ShareDelegate> shareDelegateSupplier,
             ActivityTabProvider tabProvider, ObservableSupplier<Profile> profileSupplier,
             ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             Supplier<ContextualSearchManager> contextualSearchManagerSupplier) {
         mCallbackController = new CallbackController();
         mActivity = activity;
@@ -195,8 +195,8 @@
         mOmniboxFocusStateSupplier.set(false);
 
         mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeBehaviorSupplierObserver = this::setOverviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        mOverviewModeBehaviorSupplier.onAvailable(
+                mCallbackController.makeCancelable(this::setOverviewModeBehavior));
     }
 
     // TODO(pnoland, crbug.com/865801): remove this in favor of wiring it directly.
@@ -221,9 +221,7 @@
         }
 
         if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
             mOverviewModeBehaviorSupplier = null;
-            mOverviewModeBehaviorSupplierObserver = null;
         }
 
         if (mToolbarManager != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
index cb0f24d..fccd03e7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
@@ -14,8 +14,8 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.Callback;
-import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.CallbackController;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.app.ChromeActivity;
@@ -79,8 +79,7 @@
     private final @ColorInt int mIncognitoDefaultThemeColor;
 
     private @Nullable TabModelSelector mTabModelSelector;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
+    private CallbackController mCallbackController = new CallbackController();
     private @Nullable OverviewModeBehavior.OverviewModeObserver mOverviewModeObserver;
     private @Nullable Tab mCurrentTab;
     private boolean mIsInOverviewMode;
@@ -176,34 +175,28 @@
             }
         };
 
-        mOverviewModeBehaviorSupplier = chromeActivity.getOverviewModeBehaviorSupplier();
+        OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier =
+                chromeActivity.getOverviewModeBehaviorSupplier();
+        if (overviewModeBehaviorSupplier != null) {
+            overviewModeBehaviorSupplier.onAvailable(
+                    mCallbackController.makeCancelable(overviewModeBehavior -> {
+                        assert overviewModeBehavior != null;
+                        mOverviewModeBehavior = overviewModeBehavior;
+                        mOverviewModeObserver = new EmptyOverviewModeObserver() {
+                            @Override
+                            public void onOverviewModeStartedShowing(boolean showToolbar) {
+                                mIsInOverviewMode = true;
+                                updateStatusBarColor();
+                            }
 
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplierObserver = new Callback<OverviewModeBehavior>() {
-                @Override
-                public void onResult(OverviewModeBehavior overviewModeBehavior) {
-                    assert overviewModeBehavior != null;
-                    mOverviewModeBehavior = overviewModeBehavior;
-                    mOverviewModeObserver = new EmptyOverviewModeObserver() {
-                        @Override
-                        public void onOverviewModeStartedShowing(boolean showToolbar) {
-                            mIsInOverviewMode = true;
-                            updateStatusBarColor();
-                        }
-
-                        @Override
-                        public void onOverviewModeFinishedHiding() {
-                            mIsInOverviewMode = false;
-                            updateStatusBarColor();
-                        }
-                    };
-                    mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
-                    // TODO(crbug.com/1084528): Replace with OneShotSupplier when it is available.
-                    mOverviewModeBehaviorSupplier.removeObserver(this);
-                    mOverviewModeBehaviorSupplier = null;
-                }
-            };
-            mOverviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+                            @Override
+                            public void onOverviewModeFinishedHiding() {
+                                mIsInOverviewMode = false;
+                                updateStatusBarColor();
+                            }
+                        };
+                        mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver);
+                    }));
         }
 
         chromeActivity.getLifecycleDispatcher().register(this);
@@ -213,15 +206,16 @@
     @Override
     public void destroy() {
         mStatusBarColorTabObserver.destroy();
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
-        }
         if (mOverviewModeBehavior != null) {
             mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
         }
         if (mTabModelSelector != null) {
             mTabModelSelector.removeObserver(mTabModelSelectorObserver);
         }
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
     }
 
     // TopToolbarCoordinator.UrlExpansionObserver implementation.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/tablet/emptybackground/EmptyBackgroundViewWrapper.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/tablet/emptybackground/EmptyBackgroundViewWrapper.java
index f58adb0..0ad1410 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/tablet/emptybackground/EmptyBackgroundViewWrapper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/tablet/emptybackground/EmptyBackgroundViewWrapper.java
@@ -11,8 +11,9 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.base.Callback;
+import org.chromium.base.CallbackController;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.tab.Tab;
@@ -40,8 +41,7 @@
     private final TabModelSelectorObserver mTabModelSelectorObserver;
     private final SnackbarManager mSnackbarManager;
 
-    private final ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
-    private final Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
+    CallbackController mCallbackController = new CallbackController();
     private @Nullable OverviewModeBehavior mOverviewModeBehavior;
 
     private EmptyBackgroundViewTablet mBackgroundView;
@@ -62,17 +62,15 @@
     public EmptyBackgroundViewWrapper(TabModelSelector selector, TabCreator tabCreator,
             Activity activity, @Nullable AppMenuHandler menuHandler,
             SnackbarManager snackbarManager,
-            ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
         mActivity = activity;
         mMenuHandler = menuHandler;
         mTabModelSelector = selector;
         mTabCreator = tabCreator;
         mSnackbarManager = snackbarManager;
 
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeSupplierCallback =
-                overviewModeBehavior -> mOverviewModeBehavior = overviewModeBehavior;
-        mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
+        overviewModeBehaviorSupplier.onAvailable(mCallbackController.makeCancelable(
+                overviewModeBehavior -> mOverviewModeBehavior = overviewModeBehavior));
 
         mTabModelObserver = new TabModelObserver() {
             @Override
@@ -118,7 +116,10 @@
      * Called when the containing activity is being destroyed.
      */
     public void destroy() {
-        mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarController.java
index a51ebfd..847989c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/undo_tab_close_snackbar/UndoBarController.java
@@ -8,9 +8,8 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.base.Callback;
-import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.CallbackController;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -49,8 +48,7 @@
     private final TabModelObserver mTabModelObserver;
     private final SnackbarManager.SnackbarManageable mSnackbarManagable;
     private final Context mContext;
-    private ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
-    private Callback<OverviewModeBehavior> mOverviewModeBehaviorSupplierObserver;
+    private CallbackController mCallbackController = new CallbackController();
     private OverviewModeBehavior mOverviewModeBehavior;
 
     /**
@@ -65,25 +63,13 @@
      */
     public UndoBarController(Context context, TabModelSelector selector,
             SnackbarManager.SnackbarManageable snackbarManageable,
-            ObservableSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
             @Nullable Supplier<Boolean> dialogVisibilitySupplier) {
         mSnackbarManagable = snackbarManageable;
         mTabModelSelector = selector;
         mContext = context;
-        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
-        mOverviewModeBehaviorSupplierObserver = new Callback<OverviewModeBehavior>() {
-            @Override
-            public void onResult(OverviewModeBehavior overviewModeBehavior) {
-                assert overviewModeBehavior != null;
-
-                mOverviewModeBehavior = overviewModeBehavior;
-                // TODO(crbug.com/1084528): Replace with OneShotSupplier when it is available.
-                mOverviewModeBehaviorSupplier.removeObserver(this);
-                mOverviewModeBehaviorSupplier = null;
-            }
-        };
-
-        overviewModeBehaviorSupplier.addObserver(mOverviewModeBehaviorSupplierObserver);
+        overviewModeBehaviorSupplier.onAvailable(mCallbackController.makeCancelable(
+                overviewModeBehavior -> mOverviewModeBehavior = overviewModeBehavior));
 
         mTabModelObserver = new TabModelObserver() {
             /**
@@ -172,8 +158,9 @@
     public void destroy() {
         TabModel model = mTabModelSelector.getModel(false);
         if (model != null) model.removeObserver(mTabModelObserver);
-        if (mOverviewModeBehaviorSupplier != null) {
-            mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeBehaviorSupplierObserver);
+        if (mCallbackController != null) {
+            mCallbackController.destroy();
+            mCallbackController = null;
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
index 8e7942c..634e97f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -28,6 +28,7 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -83,8 +84,8 @@
     @Mock
     private UpdateMenuItemHelper mUpdateMenuItemHelper;
 
-    private ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeSupplier =
-            new ObservableSupplierImpl<>();
+    private OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeSupplier =
+            new OneshotSupplierImpl<>();
     private ObservableSupplierImpl<BookmarkBridge> mBookmarkBridgeSupplier =
             new ObservableSupplierImpl<>();
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
index 7d7edc1..3be1ed5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -27,6 +27,7 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.R;
@@ -92,8 +93,8 @@
     @Mock
     Profile mProfileMock;
 
-    private ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeSupplier =
-            new ObservableSupplierImpl<>();
+    private OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeSupplier =
+            new OneshotSupplierImpl<>();
     private ObservableSupplierImpl<BookmarkBridge> mBookmarkBridgeSupplier =
             new ObservableSupplierImpl<>();
 
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index da2209a..652201d 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -298,6 +298,7 @@
 #define IDC_CONTENT_CONTEXT_REDO 50145
 #define IDC_CONTENT_CONTEXT_SELECTALL 50146
 #define IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE 50147
+#define IDC_CONTENT_CONTEXT_COPYLINKTOTEXT 50148
 // Other items.
 #define IDC_CONTENT_CONTEXT_TRANSLATE 50150
 #define IDC_CONTENT_CONTEXT_INSPECTELEMENT 50151
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index fafa22a4..3eb046c3 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -4579,9 +4579,6 @@
   <message name="IDS_CROSTINI_TERMINAL_STATUS_INSTALL_IMAGE_LOADER" desc="Text shown in the crostini terminal when it is checking whether the virtual machine component is installed">
     Checking the virtual machine
   </message>
-  <message name="IDS_CROSTINI_TERMINAL_STATUS_START_CONCIERGE" desc="Text shown in the crostini terminal when it is starting the VM controller">
-    Starting the virtual machine controller
-  </message>
   <message name="IDS_CROSTINI_TERMINAL_STATUS_CREATE_DISK_IMAGE" desc="Text shown in the crostini terminal when it is checking whether the VM image is installed">
     Checking the virtual machine image
   </message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_TERMINAL_STATUS_START_CONCIERGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_TERMINAL_STATUS_START_CONCIERGE.png.sha1
deleted file mode 100644
index c2dd105..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_TERMINAL_STATUS_START_CONCIERGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d5dfef986211c69d831ac4515dd9defdc772cfc3
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 84f6b77..e897344 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -583,6 +583,10 @@
             Copy link te&amp;xt
           </message>
 
+          <message name="IDS_CONTENT_CONTEXT_COPYLINKTOTEXT" desc="The name of the Copy Link to Text command in the content area context menu">
+            Copy link to text
+          </message>
+
           <message name="IDS_CONTENT_CONTEXT_SAVEIMAGEAS" desc="The name of the Save Image As command in the content area context menu">
             Sa&amp;ve image as...
           </message>
@@ -801,6 +805,10 @@
             Copy Link Te&amp;xt
           </message>
 
+          <message name="IDS_CONTENT_CONTEXT_COPYLINKTOTEXT" desc="In Title Case: The name of the Copy Link to Text command in the content area context menu">
+            Copy Link to Text
+          </message>
+
           <message name="IDS_CONTENT_CONTEXT_SAVEIMAGEAS" desc="In Title Case: The name of the Save Image As command in the content area context menu">
             Sa&amp;ve Image As...
           </message>
diff --git a/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_COPYLINKTOTEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_COPYLINKTOTEXT.png.sha1
new file mode 100644
index 0000000..571c8f1
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_CONTENT_CONTEXT_COPYLINKTOTEXT.png.sha1
@@ -0,0 +1 @@
+b7ed24ed91c5178f15b28dbb72da30e5fd53db0c
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 4f93a2c..0d8dc11 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -370,7 +370,6 @@
     "data_use_measurement/chrome_data_use_measurement.h",
     "defaults.cc",
     "defaults.h",
-    "device_oauth2_token_store.h",
     "dom_distiller/dom_distiller_service_factory.cc",
     "dom_distiller/dom_distiller_service_factory.h",
     "dom_distiller/lazy_dom_distiller_service.cc",
@@ -847,7 +846,6 @@
     "navigation_predictor/navigation_predictor_renderer_warmup_client.h",
     "navigation_predictor/search_engine_preconnector.cc",
     "navigation_predictor/search_engine_preconnector.h",
-    "net/chrome_cookie_notification_details.h",
     "net/chrome_mojo_proxy_resolver_factory.cc",
     "net/chrome_mojo_proxy_resolver_factory.h",
     "net/chrome_network_delegate.cc",
@@ -949,7 +947,6 @@
     "ntp_tiles/chrome_popular_sites_factory.h",
     "offline_items_collection/offline_content_aggregator_factory.cc",
     "offline_items_collection/offline_content_aggregator_factory.h",
-    "omnibox/common/omnibox_features.h",
     "optimization_guide/blink/blink_optimization_guide_feature_flag_helper.cc",
     "optimization_guide/blink/blink_optimization_guide_feature_flag_helper.h",
     "optimization_guide/blink/blink_optimization_guide_inquirer.cc",
@@ -1046,6 +1043,8 @@
     "page_load_metrics/observers/tab_restore_page_load_metrics_observer.h",
     "page_load_metrics/observers/third_party_metrics_observer.cc",
     "page_load_metrics/observers/third_party_metrics_observer.h",
+    "page_load_metrics/observers/translate_page_load_metrics_observer.cc",
+    "page_load_metrics/observers/translate_page_load_metrics_observer.h",
     "page_load_metrics/observers/ukm_page_load_metrics_observer.cc",
     "page_load_metrics/observers/ukm_page_load_metrics_observer.h",
     "page_load_metrics/page_load_metrics_initialize.cc",
@@ -1824,13 +1823,6 @@
   libs = []
   ldflags = []
 
-  if (is_mac) {
-    sources += [
-      "geolocation/geolocation_system_permission_mac.h",
-      "geolocation/geolocation_system_permission_mac.mm",
-    ]
-  }
-
   allow_circular_includes_from = [
     "//chrome/browser/ui",
     "//chrome/browser/ui/webui/bluetooth_internals",
@@ -1846,7 +1838,9 @@
     "//components/account_id",
     "//components/autofill/core/browser",
     "//components/nacl/common:buildflags",
+    "//components/page_info",
     "//components/payments/core",
+    "//components/payments/core:error_strings",
     "//components/policy/proto",
     "//components/safe_browsing:buildflags",
     "//components/services/storage/public/mojom",
@@ -1862,6 +1856,7 @@
     ":active_use_util",
     ":availability_protos",
     ":buildflags",
+    ":dev_ui_browser_resources_grit",
     ":expired_flags_list",
     ":ntp_background_proto",
     ":permissions_proto",
@@ -1871,7 +1866,7 @@
     ":unexpire_flags",
     "//base:i18n",
     "//base/allocator:buildflags",
-    "//base/util/memory_pressure:memory_pressure",
+    "//base/util/memory_pressure",
     "//base/util/values:values_util",
     "//build:branding_buildflags",
     "//build:chromeos_buildflags",
@@ -1879,9 +1874,12 @@
     "//chrome:extra_resources",
     "//chrome:resources",
     "//chrome:strings",
+    "//chrome/app:command_ids",
     "//chrome/app/resources:platform_locale_settings",
     "//chrome/app/theme:theme_resources",
+    "//chrome/app/vector_icons",
     "//chrome/browser/devtools",
+    "//chrome/browser/engagement:mojo_bindings",
     "//chrome/browser/image_decoder",
     "//chrome/browser/media:media_engagement_preload_proto",
     "//chrome/browser/media:mojo_bindings",
@@ -1891,25 +1889,44 @@
     "//chrome/browser/media/router",
     "//chrome/browser/metrics:expired_histograms_array",
     "//chrome/browser/metrics/variations:chrome_ui_string_overrider_factory",
+    "//chrome/browser/nearby_sharing/common",
     "//chrome/browser/net:probe_message_proto",
     "//chrome/browser/notifications",
+    "//chrome/browser/notifications/scheduler:factory",
+    "//chrome/browser/notifications/scheduler/public",
+    "//chrome/browser/policy:path_parser",
     "//chrome/browser/privacy_budget",
     "//chrome/browser/profiling_host",
+    "//chrome/browser/promo_browser_command:mojo_bindings",
     "//chrome/browser/push_messaging:budget_proto",
     "//chrome/browser/reputation:proto",
     "//chrome/browser/resource_coordinator:mojo_bindings",
     "//chrome/browser/resource_coordinator:tab_manager_features",
     "//chrome/browser/safe_browsing",
+    "//chrome/browser/safe_browsing:advanced_protection",
+    "//chrome/browser/safe_browsing:url_lookup_service_factory",
     "//chrome/browser/sharing:buildflags",
     "//chrome/browser/sharing/proto",
     "//chrome/browser/storage_access_api:permissions",
     "//chrome/browser/thumbnail",
     "//chrome/browser/touch_to_fill",
     "//chrome/browser/ui",
+    "//chrome/browser/ui/webui/app_management:mojo_bindings",
     "//chrome/browser/ui/webui/bluetooth_internals",
+    "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings",
+    "//chrome/browser/ui/webui/downloads:mojo_bindings",
+    "//chrome/browser/ui/webui/internals/web_app:mojo_bindings",
+    "//chrome/browser/ui/webui/interventions_internals:mojo_bindings",
+    "//chrome/browser/ui/webui/new_tab_page:mojo_bindings",
+    "//chrome/browser/ui/webui/omnibox:mojo_bindings",
+    "//chrome/browser/ui/webui/read_later:mojo_bindings",
+    "//chrome/browser/ui/webui/reset_password:mojo_bindings",
+    "//chrome/browser/ui/webui/tab_search:mojo_bindings",
+    "//chrome/browser/ui/webui/usb_internals:mojo_bindings",
     "//chrome/browser/updates/announcement_notification",
     "//chrome/browser/video_tutorials",
     "//chrome/common:channel_info",
+    "//chrome/common:version_header",
     "//chrome/common/net",
     "//chrome/common/performance_manager/mojom",
     "//chrome/installer/util:with_no_strings",
@@ -1919,7 +1936,8 @@
     "//components/autofill/content/browser",
     "//components/autofill/core/browser",
     "//components/background_task_scheduler",
-    "//components/blocklist/opt_out_blocklist:opt_out_blocklist",
+    "//components/blocked_content",
+    "//components/blocklist/opt_out_blocklist",
     "//components/blocklist/opt_out_blocklist/sql:opt_out_blocklist_sql",
     "//components/bookmarks/browser",
     "//components/bookmarks/managed",
@@ -1939,9 +1957,12 @@
     "//components/consent_auditor",
     "//components/content_capture/browser",
     "//components/content_settings/browser",
+    "//components/content_settings/common:mojom",
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
+    "//components/contextual_search:buildflags",
     "//components/contextual_search/content:browser",
+    "//components/contextual_search/content/common/mojom",
     "//components/contextual_search/core:browser",
     "//components/cookie_config",
     "//components/country_codes",
@@ -1953,6 +1974,7 @@
     "//components/dom_distiller/content/common/mojom",
     "//components/domain_reliability",
     "//components/download/content/factory",
+    "//components/download/content/public",
     "//components/download/database",
     "//components/download/public/background_service:public",
     "//components/embedder_support",
@@ -1960,12 +1982,18 @@
     "//components/enterprise",
     "//components/enterprise/common/proto:connectors_proto",
     "//components/error_page/common",
+    "//components/error_page/content/browser",
     "//components/favicon/content",
     "//components/favicon/core",
     "//components/favicon/core:history_implementation",
     "//components/favicon_base",
     "//components/feature_engagement",
     "//components/federated_learning",
+    "//components/feed:buildflags",
+    "//components/feed/content:feed_content",
+    "//components/feed/core/common:feed_core_common",
+    "//components/feed/core/shared_prefs:feed_shared_prefs",
+    "//components/feed/core/v2:feed_core_v2",
     "//components/filename_generation",
     "//components/find_in_page",
     "//components/flags_ui",
@@ -1981,11 +2009,15 @@
     "//components/javascript_dialogs",
     "//components/keyed_service/content",
     "//components/language/content/browser",
+    "//components/language/content/browser/ulp_language_code_locator",
     "//components/language/core/browser",
     "//components/language/core/common",
+    "//components/language_usage_metrics",
     "//components/leveldb_proto",
     "//components/lookalikes/core",
+    "//components/lookalikes/core:features",
     "//components/metrics:call_stack_profile_collector",
+    "//components/metrics:call_stack_profile_params",
     "//components/metrics:component_metrics",
     "//components/metrics:content",
     "//components/metrics:demographic_metrics_provider",
@@ -2001,14 +2033,23 @@
     "//components/ntp_tiles",
     "//components/offline_items_collection/core",
     "//components/offline_pages/buildflags",
+    "//components/offline_pages/core",
+    "//components/offline_pages/core/background:background_offliner",
+    "//components/offline_pages/core/prefetch",
+    "//components/offline_pages/core/request_header",
     "//components/omnibox/browser",
+    "//components/onc",
+    "//components/open_from_clipboard",
     "//components/optimization_guide",
     "//components/os_crypt",
+    "//components/page_load_metrics/browser",
+    "//components/page_load_metrics/common",
     "//components/paint_preview/buildflags",
     "//components/paint_preview/features",
     "//components/password_manager/content/browser",
     "//components/password_manager/core/browser",
     "//components/password_manager/core/common",
+    "//components/payments/content:utils",
     "//components/payments/core",
     "//components/performance_manager",
     "//components/permissions",
@@ -2024,15 +2065,27 @@
     "//components/prerender/common:mojo_bindings",
     "//components/previews/content",
     "//components/previews/core",
+    "//components/printing/browser",
     "//components/profile_metrics",
     "//components/proxy_config",
     "//components/query_parser",
     "//components/query_tiles",
     "//components/rappor",
+    "//components/reading_list/core",
     "//components/renderer_context_menu",
     "//components/resources",
+    "//components/safe_browsing/content/browser",
+    "//components/safe_browsing/content/password_protection",
+    "//components/safe_browsing/core:download_file_types_proto",
+    "//components/safe_browsing/core:features",
+    "//components/safe_browsing/core:file_type_policies",
     "//components/safe_browsing/core:public",
+    "//components/safe_browsing/core/browser",
+    "//components/safe_browsing/core/common",
     "//components/safe_browsing/core/common:safe_browsing_policy_handler",
+    "//components/safe_browsing/core/db:database_manager",
+    "//components/safe_browsing/core/realtime:policy_engine",
+    "//components/safe_browsing/core/realtime:url_lookup_service",
     "//components/safe_search_api",
     "//components/safe_search_api:safe_search_client",
     "//components/schema_org/common:improved_mojom",
@@ -2067,31 +2120,37 @@
     "//components/strings",
     "//components/subresource_filter/content/browser",
     "//components/subresource_filter/core/browser",
+    "//components/subresource_filter/core/common",
     "//components/suggestions",
     "//components/sync",
     "//components/sync_bookmarks",
     "//components/sync_preferences",
     "//components/sync_sessions",
     "//components/tab_count_metrics",
+    "//components/tab_groups",
     "//components/tracing:startup_tracing",
     "//components/translate/content/browser",
     "//components/translate/core/browser",
     "//components/translate/core/common",
     "//components/ui_devtools",
+    "//components/ukm",
     "//components/ukm:observers",
     "//components/undo",
     "//components/update_client",
     "//components/update_client:common_impl",
     "//components/upload_list",
     "//components/url_formatter",
+    "//components/url_formatter/spoof_checks/top_domains:common",
     "//components/url_formatter/spoof_checks/top_domains:top500_domains",
     "//components/url_formatter/spoof_checks/top_domains:top500_domains_header",
     "//components/url_matcher",
+    "//components/user_manager",
     "//components/user_prefs",
     "//components/variations",
     "//components/variations/field_trial_config",
     "//components/variations/net",
     "//components/variations/service",
+    "//components/vector_icons",
     "//components/version_info",
     "//components/visitedlink/browser",
     "//components/visitedlink/common",
@@ -2104,7 +2163,6 @@
     "//components/webrtc_logging/common",
     "//content/app/resources",
     "//content/public/browser",
-    "//content/public/browser",
     "//content/public/common",
     "//content/public/common:service_names",
     "//crypto",
@@ -2119,6 +2177,7 @@
     "//media",
     "//media:media_buildflags",
     "//media/capture",
+    "//media/capture:capture_switches",
     "//media/midi",
     "//media/mojo:buildflags",
     "//media/mojo/common",
@@ -2129,6 +2188,8 @@
     "//net",
     "//net:extras",
     "//ppapi/buildflags",
+    "//ppapi/host",
+    "//printing",
     "//printing/buildflags",
     "//rlz/buildflags",
     "//services/audio/public/cpp",
@@ -2153,6 +2214,7 @@
     "//services/proxy_resolver/public/mojom",
     "//services/resource_coordinator/public/cpp:resource_coordinator_cpp",
     "//services/resource_coordinator/public/cpp/memory_instrumentation:browser",
+    "//services/service_manager/embedder:embedder_result_codes",
     "//services/service_manager/public/cpp",
     "//services/shape_detection/public/mojom",
     "//services/strings",
@@ -2173,6 +2235,7 @@
     "//third_party/re2",
     "//third_party/webrtc_overrides:webrtc_component",
     "//third_party/widevine/cdm:buildflags",
+    "//third_party/widevine/cdm:headers",
     "//third_party/zlib",
     "//third_party/zlib:minizip",
     "//third_party/zlib/google:compression_utils",
@@ -2181,9 +2244,12 @@
     "//ui/accessibility",
     "//ui/base",
     "//ui/base:ui_data_pack",
+    "//ui/base/clipboard",
     "//ui/base/idle",
     "//ui/base/ime",
+    "//ui/compositor",
     "//ui/events:events_base",
+    "//ui/events/devices",
     "//ui/gfx",
     "//ui/gfx/geometry",
     "//ui/gl",
@@ -2192,10 +2258,63 @@
     "//ui/message_center/public/cpp",
     "//ui/resources",
     "//ui/shell_dialogs",
+    "//ui/snapshot",
     "//ui/strings",
     "//ui/surface",
     "//ui/web_dialogs",
+    "//ui/webui",
+    "//ui/webui/resources/cr_components/customize_themes:mojom",
   ]
+  if (is_chromeos) {
+    deps += [
+      "//chrome/app/theme:chrome_unscaled_resources_grit",
+      "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/file_manager:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/machine_learning:mojo_bindings",
+      "//chrome/browser/ui/webui/settings/chromeos/constants:mojom",
+      "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings",
+      "//chromeos/audio",
+      "//chromeos/components/camera_app_ui",
+      "//chromeos/components/camera_app_ui:mojo_bindings",
+      "//chromeos/components/help_app_ui",
+      "//chromeos/components/help_app_ui:mojo_bindings",
+      "//chromeos/components/local_search_service",
+      "//chromeos/components/media_app_ui",
+      "//chromeos/components/media_app_ui:mojo_bindings",
+      "//chromeos/components/multidevice/debug_webui",
+      "//chromeos/components/print_management",
+      "//chromeos/components/print_management/mojom",
+      "//chromeos/components/quick_answers/public/cpp:prefs",
+      "//chromeos/components/scanning",
+      "//chromeos/components/scanning/mojom",
+      "//chromeos/dbus/cryptohome",
+      "//chromeos/dbus/power",
+      "//chromeos/services/device_sync/public/cpp:prefs",
+      "//chromeos/services/multidevice_setup/public/cpp",
+      "//chromeos/services/multidevice_setup/public/cpp:prefs",
+      "//chromeos/system",
+      "//chromeos/timezone",
+      "//chromeos/tpm",
+      "//components/drive",
+      "//components/quirks",
+      "//components/session_manager/core",
+      "//device/vr/public/mojom",
+    ]
+    if (!is_official_build) {
+      deps += [
+        "//chromeos/components/telemetry_extension_ui",
+        "//chromeos/components/telemetry_extension_ui/mojom",
+      ]
+    }
+  }
+  if (use_ozone) {
+    deps += [
+      "//ui/events/ozone",
+      "//ui/ozone",
+    ]
+  }
 
   if (build_with_tflite_lib) {
     sources += [
@@ -2209,7 +2328,7 @@
       "tflite_experiment/tflite_experiment_switches.h",
     ]
 
-    deps += [ "//chrome/services/machine_learning:machine_learning" ]
+    deps += [ "//chrome/services/machine_learning" ]
   }
 
   if (is_posix || is_fuchsia) {
@@ -2871,7 +2990,6 @@
       "password_manager/android/password_scripts_fetcher_android.cc",
       "password_manager/android/save_password_infobar_delegate_android.cc",
       "password_manager/android/save_password_infobar_delegate_android.h",
-      "password_manager/android/touch_to_fill_view.h",
       "password_manager/android/update_password_infobar_delegate_android.cc",
       "password_manager/android/update_password_infobar_delegate_android.h",
       "password_manager/biometric_authenticator_android.cc",
@@ -2916,7 +3034,6 @@
       "search/contextual_search_policy_handler_android.cc",
       "search/contextual_search_policy_handler_android.h",
       "search_engines/template_url_service_factory_android.cc",
-      "search_engines/template_url_service_factory_android.h",
       "search_engines/ui_thread_search_terms_data_android.cc",
       "search_engines/ui_thread_search_terms_data_android.h",
       "sessions/session_restore_android.cc",
@@ -2981,17 +3098,27 @@
       "//chrome/browser/offline_pages/prefetch/notifications",
       "//chrome/browser/optimization_guide/android:jni_headers",
       "//chrome/browser/password_check/android:jni_headers",
+      "//chrome/browser/password_check/android:password_check_enums_srcjar",
       "//chrome/browser/password_manager/android:jni_headers",
       "//chrome/browser/payments/android:jni_headers",
       "//chrome/browser/policy/android:jni_headers",
       "//chrome/browser/privacy:jni_headers",
       "//chrome/browser/safety_check/android",
       "//chrome/browser/share",
+      "//chrome/browser/share/android:jni_headers",
       "//chrome/browser/tab:jni_headers",
+      "//chrome/browser/ui/webui/explore_sites_internals:mojo_bindings",
+      "//chrome/browser/ui/webui/snippets_internals:mojo_bindings",
       "//chrome/browser/updates",
+      "//chrome/browser/updates:factory",
+      "//chrome/browser/video_tutorials/internal",
+      "//chrome/browser/video_tutorials/internal:jni_headers",
       "//chrome/common:non_code_constants",
       "//chrome/services/media_gallery_util/public/cpp",
+      "//components/assist_ranker/proto",
       "//components/autofill_assistant/browser",
+      "//components/autofill_assistant/browser:proto",
+      "//components/bookmarks/common/android",
       "//components/browser_ui/contacts_picker/android",
       "//components/browser_ui/photo_picker/android",
       "//components/browser_ui/site_settings/android",
@@ -3003,31 +3130,46 @@
       "//components/content_settings/android",
       "//components/crash/android:crash_android",
       "//components/embedder_support/android:browser_context",
+      "//components/embedder_support/android:context_menu",
       "//components/embedder_support/android:util",
       "//components/embedder_support/android:web_contents_delegate",
       "//components/external_intents/android",
+      "//components/favicon/core:database",
       "//components/feed:buildflags",
       "//components/feed:feature_list",
+      "//components/image_fetcher/core:metrics",
+      "//components/infobars/android",
       "//components/invalidation/impl:feature_list",
       "//components/javascript_dialogs/android:jni_headers",
       "//components/language/android:language_bridge",
       "//components/location/android:settings",
       "//components/messages/android:feature_flags",
       "//components/module_installer/android:native",
+      "//components/offline_pages/task",
       "//components/omnibox/browser",
       "//components/page_info/android",
       "//components/page_load_metrics/browser",
+      "//components/paint_preview/browser",
       "//components/password_manager/content/browser",
+      "//components/password_manager/core/browser:affiliation",
+      "//components/password_manager/core/browser/leak_detection",
+      "//components/payments/content",
       "//components/payments/content/android",
+      "//components/payments/content/android:jni_headers",
       "//components/permissions/android:native",
       "//components/query_tiles",
+      "//components/resources:android_resources",
       "//components/resources:components_resources",
+      "//components/safe_browsing/content/password_protection:password_protection_metrics_util",
       "//components/security_state/content/android",
       "//components/send_tab_to_self",
       "//components/signin/internal/identity_manager",  # cf android / signin /
                                                         # DEPS
+      "//components/signin/public/android:jni_headers",
       "//components/subresource_filter/android",
       "//components/thin_webview/internal",
+      "//components/translate/core/language_detection",
+      "//components/ukm/content",
       "//components/viz/common",
       "//ipc:param_traits",
       "//media/mojo/clients",
@@ -3035,14 +3177,17 @@
       "//sandbox",
       "//sandbox:sandbox_buildflags",
       "//services/device/public/cpp:device_feature_list",
+      "//services/device/public/cpp/geolocation",
       "//services/proxy_resolver:lib",
       "//third_party/android_ndk:cpu_features",
       "//third_party/android_opengl/etc1",
       "//third_party/blink/public/common",
-      "//third_party/crashpad/crashpad/client:client",
+      "//third_party/blink/public/mojom:android_mojo_bindings_blink_headers",
+      "//third_party/crashpad/crashpad/client",
       "//third_party/libaddressinput:util",
       "//third_party/libphonenumber",
       "//third_party/smhasher:murmurhash2",
+      "//url:gurl_android",
       "//url:origin_android",
     ]
     allow_circular_includes_from += [ "//chrome/browser/share" ]
@@ -3841,11 +3986,14 @@
       ":theme_properties",
       "//base/util/memory_pressure",
       "//base/util/timer",
+      "//chrome/app:command_ids",
+      "//chrome/app/theme:chrome_unscaled_resources_grit",
       "//chrome/app/vector_icons",
+      "//chrome/browser/media/kaleidoscope:kaleidoscope_resources",
+      "//chrome/browser/media/kaleidoscope/mojom",
       "//chrome/browser/nearby_sharing:share_target",
       "//chrome/browser/nearby_sharing/certificates",
       "//chrome/browser/nearby_sharing/client",
-      "//chrome/browser/nearby_sharing/common",
       "//chrome/browser/nearby_sharing/contacts",
       "//chrome/browser/nearby_sharing/instantmessaging/proto",
       "//chrome/browser/nearby_sharing/local_device_data",
@@ -3860,18 +4008,29 @@
       "//chrome/browser/resource_coordinator/tab_ranker",
       "//chrome/browser/resources:component_extension_resources",
       "//chrome/browser/search:generated",
+      "//chrome/browser/ui/color:color_headers",
+      "//chrome/browser/ui/webui/nearby_share:mojom",
+      "//chrome/browser/ui/webui/nearby_share/public/mojom",
+      "//chrome/common/apps/platform_apps",
       "//chrome/common/importer:interfaces",
       "//chrome/common/search:generate_chrome_colors_info",
+      "//chrome/common/search:generate_colors_info",
       "//chrome/common/themes:autogenerated_theme_util",
+      "//chrome/services/media_gallery_util/public/cpp",
       "//chrome/services/sharing/public/cpp",
       "//chrome/services/sharing/public/mojom",
       "//chrome/services/sharing/public/proto",
       "//chrome/services/speech:buildflags",
+      "//components/download/quarantine",
       "//components/feedback",
       "//components/image_fetcher/core",
       "//components/keep_alive_registry",
       "//components/ntp_snippets",
+      "//components/pref_registry",
+      "//components/printing/common:mojo_interfaces",
+      "//components/schema_org",
       "//components/schema_org:extractor",
+      "//components/schema_org:schema_org_properties",
       "//components/services/app_service:lib",
       "//components/services/app_service/public/cpp:app_file_handling",
       "//components/services/app_service/public/cpp:app_share_target",
@@ -3881,11 +4040,27 @@
       "//components/services/app_service/public/cpp:preferred_apps",
       "//components/services/app_service/public/cpp:publisher",
       "//components/soda:constants",
-      "//components/vector_icons",
+      "//components/ukm/content",
       "//components/web_modal",
       "//components/zoom",
       "//courgette:courgette_lib",
+      "//third_party/sqlite",
     ]
+    if (is_win) {
+      # TODO (crbug.com/1126800): Include this dep when build bots are fixed.
+      deps -= [ "//third_party/sqlite" ]
+    }
+    if (is_chromeos) {
+      deps += [
+        "//chrome/browser/supervised_user:supervised_user_unscaled_resources",
+        "//chromeos/dbus/session_manager",
+        "//chromeos/login/session",
+        "//components/arc:arc_base_utils",
+      ]
+    }
+    if (enable_extensions) {
+      deps += [ "//extensions/browser" ]
+    }
 
     if (is_posix || is_fuchsia) {
       sources += [
@@ -4077,18 +4252,28 @@
       "upgrade_detector/upgrade_detector_chromeos.h",
     ]
     deps += [
+      "//ash",
       "//ash/public/cpp",
       "//ash/public/cpp/external_arc",
+      "//ash/public/cpp/vector_icons",
       "//chrome/browser/chromeos",
+      "//chrome/browser/chromeos/power/ml/smart_dim",
       "//chromeos/components/account_manager",
       "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_browser",
       "//chromeos/components/quick_answers",
       "//chromeos/components/sync_wifi",
       "//chromeos/crosapi/mojom",
+      "//chromeos/dbus",
+      "//chromeos/geolocation",
+      "//chromeos/login/login_state",
+      "//chromeos/memory",
       "//chromeos/memory/userspace_swap",
       "//chromeos/memory/userspace_swap:mojom",
+      "//chromeos/network",
       "//chromeos/services/assistant/public/cpp",
       "//chromeos/services/cellular_setup",
+      "//chromeos/services/cros_healthd/public/cpp",
+      "//chromeos/services/cros_healthd/public/mojom",
       "//chromeos/services/device_sync/public/cpp:prefs",
       "//chromeos/services/device_sync/public/mojom",
       "//chromeos/services/multidevice_setup",
@@ -4097,7 +4282,11 @@
       "//chromeos/services/network_config",
       "//chromeos/services/network_config/public/mojom",
       "//chromeos/services/secure_channel/public/mojom",
+      "//chromeos/settings",
       "//chromeos/strings",
+      "//components/arc",
+      "//components/arc:arc_base",
+      "//components/arc/mojom",
       "//components/metrics/structured",
       "//components/services/app_service/public/cpp:instance_update",
       "//components/services/font:lib",
@@ -4106,6 +4295,7 @@
       "//ui/chromeos",
       "//ui/events/ozone",
       "//ui/ozone",
+      "//ui/wm/public",
     ]
     allow_circular_includes_from += [ "//chrome/browser/chromeos" ]
   } else {  # Non - ChromeOS.
@@ -4170,6 +4360,8 @@
       "comsuppw.lib",
     ]
     sources += [
+      "accessibility/caption_settings_dialog.h",
+      "accessibility/caption_settings_dialog_win.cc",
       "badging/badge_manager_delegate_win.cc",
       "badging/badge_manager_delegate_win.h",
       "browser_process_platform_part_win.cc",
@@ -4310,22 +4502,32 @@
     ]
     deps += [
       ":chrome_process_finder",
+      "//base/win:base_win_buildflags",
+      "//chrome/app/theme:chrome_unscaled_resources_grit",
       "//chrome/browser/safe_browsing/chrome_cleaner",
       "//chrome/browser/safe_browsing/chrome_cleaner:public",
+      "//chrome/browser/web_applications/chrome_pwa_launcher:util",
       "//chrome/browser/win/conflicts:module_info",
       "//chrome/chrome_elf:constants",
       "//chrome/chrome_elf:dll_hash",
+      "//chrome/chrome_elf:third_party_shared_defines",
       "//chrome/common:version_header",
       "//chrome/credential_provider/common:common_constants",
       "//chrome/elevation_service:elevation_service_idl",
       "//chrome/install_static:install_static_util",
+      "//chrome/installer/util:with_no_strings",
       "//chrome/notification_helper:constants",
       "//chrome/services/util_win/public/mojom",
       "//components/browser_watcher:browser_watcher_client",
       "//components/browser_watcher:stability_client",
       "//components/chrome_cleaner/public/constants",
+      "//components/crash/core/app",
+      "//components/crash/core/app:crash_export_thunk_include",
       "//components/download/quarantine",
-      "//third_party/crashpad/crashpad/client:client",
+      "//components/services/quarantine/public/cpp:features",
+      "//google_update",
+      "//sandbox/win:sandbox",
+      "//third_party/crashpad/crashpad/client",
       "//third_party/iaccessible2",
       "//third_party/isimpledom",
       "//third_party/wtl",
@@ -4411,6 +4613,8 @@
   if (is_mac) {
     allow_circular_includes_from += [ "//chrome/browser/apps/app_shim" ]
     sources += [
+      "accessibility/caption_settings_dialog.h",
+      "accessibility/caption_settings_dialog_mac.mm",
       "app_controller_mac.h",
       "app_controller_mac.mm",
       "apps/intent_helper/mac_apps_navigation_throttle.h",
@@ -4429,6 +4633,8 @@
       "first_run/first_run_internal_mac.mm",
       "first_run/upgrade_util_mac.cc",
       "fullscreen_mac.mm",
+      "geolocation/geolocation_system_permission_mac.h",
+      "geolocation/geolocation_system_permission_mac.mm",
       "global_keyboard_shortcuts_mac.h",
       "global_keyboard_shortcuts_mac.mm",
       "hang_monitor/hang_crash_dump_mac.cc",
@@ -4484,9 +4690,10 @@
       "//chrome/app_shim",
       "//chrome/browser/apps/app_shim",
       "//chrome/browser/ui/cocoa/notifications:common",
+      "//components/crash/core/app",
       "//components/metal_util",
       "//services/video_capture/public/mojom:constants",
-      "//third_party/crashpad/crashpad/client:client",
+      "//third_party/crashpad/crashpad/client",
       "//third_party/google_toolbox_for_mac",
       "//third_party/mozilla",
     ]
@@ -4551,6 +4758,7 @@
       "themes/theme_service_aura_linux.h",
       "upgrade_detector/get_installed_version_linux.cc",
     ]
+    deps += [ "//chrome/app/theme:chrome_unscaled_resources_grit" ]
 
     if (use_dbus) {
       deps += [ "//components/dbus/thread_linux" ]
@@ -4608,20 +4816,6 @@
     ]
   }
 
-  if (is_mac) {
-    sources += [
-      "accessibility/caption_settings_dialog.h",
-      "accessibility/caption_settings_dialog_mac.mm",
-    ]
-  }
-
-  if (is_win) {
-    sources += [
-      "accessibility/caption_settings_dialog.h",
-      "accessibility/caption_settings_dialog_win.cc",
-    ]
-  }
-
   if (is_win || is_chromeos) {
     sources += [
       "webshare/share_service_impl.cc",
@@ -4771,6 +4965,10 @@
     sources += [
       "crash_upload_list/crash_upload_list_crashpad.cc",
       "crash_upload_list/crash_upload_list_crashpad.h",
+      "media/cast_mirroring_service_host.cc",
+      "media/cast_mirroring_service_host.h",
+      "media/offscreen_tab.cc",
+      "media/offscreen_tab.h",
       "payments/chrome_payment_request_delegate.cc",
       "payments/chrome_payment_request_delegate.h",
       "payments/payment_credential_factory.cc",
@@ -4780,7 +4978,16 @@
       "payments/payment_request_factory.cc",
       "payments/payment_request_factory.h",
     ]
-    deps += [ "//components/payments/content" ]
+    deps += [
+      "//chrome/browser/ui/webui/discards:mojo_bindings",
+      "//components/autofill/content/browser/webauthn",
+      "//components/mirroring/browser",
+      "//components/mirroring/mojom:host",
+      "//components/mirroring/mojom:service",
+      "//components/mirroring/service:mirroring_service",
+      "//components/payments/content",
+      "//media/cast:net",
+    ]
   }
 
   if (is_win || is_mac || (is_chromeos && use_dbus)) {
@@ -4817,6 +5024,7 @@
       "//ui/base/dragdrop/mojom:mojom_shared",
       "//ui/compositor",
       "//ui/snapshot",
+      "//ui/wm",
     ]
     if (use_gtk) {
       deps += [ "//ui/gtk" ]
@@ -4835,6 +5043,7 @@
       "chrome_browser_main_extra_parts_ozone.cc",
       "chrome_browser_main_extra_parts_ozone.h",
     ]
+    deps += [ "//ui/ozone" ]
   }
 
   if (enable_background_mode) {
@@ -5020,22 +5229,6 @@
     }
   }
 
-  if (is_win || is_mac || is_linux || is_chromeos) {
-    sources += [
-      "media/cast_mirroring_service_host.cc",
-      "media/cast_mirroring_service_host.h",
-      "media/offscreen_tab.cc",
-      "media/offscreen_tab.h",
-    ]
-    deps += [
-      "//components/mirroring/browser",
-      "//components/mirroring/mojom:host",
-      "//components/mirroring/mojom:service",
-      "//components/mirroring/service:mirroring_service",
-      "//media/cast:net",
-    ]
-  }
-
   if (enable_media_remoting) {
     sources += [
       "media/cast_remoting_connector.cc",
@@ -5252,6 +5445,7 @@
       "//chrome/common/extensions/api:extensions_features",
       "//components/drive",
       "//components/guest_view/browser",
+      "//media/cast:sender",
 
       # TODO(crbug.com/879012): mirroring shouldn't depend on enable_extensions.
       "//components/mirroring/browser",
@@ -5262,6 +5456,9 @@
       "//google_apis/drive",
       "//services/device/public/mojom",
     ]
+    if (is_chromeos) {
+      deps += [ "//chromeos/services/tts/public/mojom" ]
+    }
   }
 
   if (enable_feed_in_chrome) {
@@ -5299,6 +5496,7 @@
       "android/feed/v2/refresh_task_scheduler_impl.h",
     ]
     deps += [
+      "//chrome/browser/ui/webui/feed_internals:mojo_bindings",
       "//components/feed/content:feed_content",
       "//components/feed/core/v2:feed_core_v2",
     ]
@@ -5470,7 +5668,7 @@
       "//components/offline_pages/content/renovations",
       "//components/offline_pages/core/downloads:offline_pages_ui_adapter",
       "//components/offline_pages/core/renovations",
-      "//components/offline_pages/core/request_header:request_header",
+      "//components/offline_pages/core/request_header",
     ]
 
     # Used to build test harness locally.The harness is used manually to
@@ -5577,10 +5775,14 @@
       "//components/pdf/browser",
       "//media:media_buildflags",
       "//ppapi/buildflags",
+      "//ppapi/host",
       "//ppapi/proxy:ipc",
       "//services/device/public/mojom",
       "//third_party/adobe/flash:flapper_version_h",
     ]
+    if (is_chromeos) {
+      deps += [ "//chromeos/cryptohome" ]
+    }
   }
 
   if (enable_rlz) {
@@ -5678,6 +5880,7 @@
       "sessions/tab_loader_delegate.cc",
       "sessions/tab_loader_delegate.h",
     ]
+    deps += [ "//components/tab_groups" ]
   }
 
   if (enable_spellcheck) {
@@ -5784,7 +5987,9 @@
     ]
     deps += [
       "//chrome/browser/supervised_user/kids_chrome_management:proto",
+      "//chrome/browser/supervised_user/supervised_user_error_page",
       "//chrome/common:supervised_user_commands_mojom",
+      "//components/pref_registry",
     ]
   }
   if (enable_supervised_users && enable_extensions) {
@@ -5823,6 +6028,14 @@
       "//chrome/browser/vr:vr_common",
       "//device/vr",
     ]
+
+    if (is_android) {
+      if (enable_arcore) {
+        deps += [ "//device/vr/android/arcore" ]
+      }
+    } else {
+      deps += [ "//device/vr/public/mojom" ]
+    }
   }
 
   if (enable_wayland_server) {
@@ -6177,8 +6390,6 @@
     "net/dns_probe_test_util.h",
     "notifications/metrics/mock_notification_metrics_logger.cc",
     "notifications/metrics/mock_notification_metrics_logger.h",
-    "notifications/notification_display_service_tester.cc",
-    "notifications/notification_display_service_tester.h",
     "notifications/notification_test_util.cc",
     "notifications/notification_test_util.h",
     "notifications/stub_notification_display_service.cc",
@@ -6191,16 +6402,10 @@
     "reputation/safety_tip_test_utils.h",
     "resource_coordinator/tab_load_tracker_test_support.cc",
     "resource_coordinator/tab_load_tracker_test_support.h",
-    "search_engines/template_url_service_factory_test_util.cc",
-    "search_engines/template_url_service_factory_test_util.h",
-    "search_engines/template_url_service_test_util.cc",
-    "search_engines/template_url_service_test_util.h",
     "signin/chrome_signin_client_test_util.cc",
     "signin/chrome_signin_client_test_util.h",
     "signin/e2e_tests/test_accounts_util.cc",
     "signin/e2e_tests/test_accounts_util.h",
-    "signin/identity_test_environment_profile_adaptor.cc",
-    "signin/identity_test_environment_profile_adaptor.h",
     "ssl/ssl_browsertest_util.cc",
     "ssl/ssl_browsertest_util.h",
     "ssl/ssl_client_auth_requestor_mock.cc",
@@ -6221,10 +6426,12 @@
   deps = [
     "//chrome/app/theme:theme_resources",
     "//chrome/browser",
+    "//chrome/browser/reputation:proto",
     "//chrome/browser/subresource_filter:test_support",
     "//chrome/common",
     "//chrome/common/safe_browsing:proto",
     "//components/browser_sync:test_support",
+    "//components/consent_auditor:test_support",
     "//components/invalidation/impl",
     "//components/invalidation/impl:test_support",
     "//components/password_manager/core/browser:test_support",
@@ -6232,6 +6439,7 @@
     "//components/prefs:test_support",
     "//components/safe_browsing/core:csd_proto",
     "//components/search_engines:test_support",
+    "//components/security_interstitials/content:security_interstitial_page",
     "//components/services/unzip/content",
     "//components/sessions:test_support",
     "//components/signin/public/identity_manager:test_support",
@@ -6244,6 +6452,7 @@
     "//google_apis:test_support",
     "//net:test_support",
     "//services/data_decoder/public/cpp:test_support",
+    "//services/network/public/proto:tls_deprecation_config_proto",
     "//services/preferences/public/cpp/tracked:test_support",
     "//skia",
     "//testing/gmock",
@@ -6314,16 +6523,8 @@
       "chromeos/login/screens/mock_device_disabled_screen_view.h",
       "chromeos/login/session/user_session_manager_test_api.cc",
       "chromeos/login/session/user_session_manager_test_api.h",
-      "chromeos/login/test/js_checker.cc",
-      "chromeos/login/test/js_checker.h",
-      "chromeos/login/test/oobe_auth_page_waiter.cc",
-      "chromeos/login/test/oobe_auth_page_waiter.h",
       "chromeos/login/test/oobe_configuration_waiter.cc",
       "chromeos/login/test/oobe_configuration_waiter.h",
-      "chromeos/login/test/oobe_screen_exit_waiter.cc",
-      "chromeos/login/test/oobe_screen_exit_waiter.h",
-      "chromeos/login/test/oobe_screen_waiter.cc",
-      "chromeos/login/test/oobe_screen_waiter.h",
       "chromeos/login/ui/fake_login_display_host.cc",
       "chromeos/login/ui/fake_login_display_host.h",
       "chromeos/login/ui/mock_login_display.cc",
@@ -6332,8 +6533,6 @@
       "chromeos/login/ui/mock_login_display_host.h",
       "chromeos/login/users/avatar/mock_user_image_manager.cc",
       "chromeos/login/users/avatar/mock_user_image_manager.h",
-      "chromeos/login/users/fake_chrome_user_manager.cc",
-      "chromeos/login/users/fake_chrome_user_manager.h",
       "chromeos/login/users/fake_supervised_user_manager.cc",
       "chromeos/login/users/fake_supervised_user_manager.h",
       "chromeos/login/users/mock_user_manager.cc",
@@ -6348,15 +6547,24 @@
       "chromeos/policy/fake_device_cloud_policy_initializer.h",
       "chromeos/policy/fake_device_cloud_policy_manager.cc",
       "chromeos/policy/fake_device_cloud_policy_manager.h",
-      "chromeos/settings/device_settings_test_helper.cc",
-      "chromeos/settings/device_settings_test_helper.h",
       "ui/app_list/test/chrome_app_list_test_support.cc",
       "ui/app_list/test/chrome_app_list_test_support.h",
       "ui/app_list/test/test_app_list_controller_delegate.cc",
       "ui/app_list/test/test_app_list_controller_delegate.h",
     ]
     configs += [ "//build/config/linux/dbus" ]
-    deps += [ "//chromeos:test_support" ]
+    deps += [
+      "//ash/public/cpp",
+      "//chrome/test:test_support_ui",
+      "//chromeos:test_support",
+      "//chromeos/attestation:test_support",
+      "//chromeos/dbus",
+      "//chromeos/disks",
+      "//chromeos/login/auth",
+      "//components/session_manager/core",
+      "//ui/base:test_support",
+      "//ui/base/ime/chromeos",
+    ]
   }
 
   if (is_win) {
@@ -6382,10 +6590,6 @@
       "extensions/test_blocklist.h",
       "extensions/test_blocklist_state_fetcher.cc",
       "extensions/test_blocklist_state_fetcher.h",
-      "extensions/test_extension_environment.cc",
-      "extensions/test_extension_environment.h",
-      "extensions/test_extension_prefs.cc",
-      "extensions/test_extension_prefs.h",
       "extensions/test_extension_service.cc",
       "extensions/test_extension_service.h",
       "extensions/test_extension_system.cc",
@@ -6394,13 +6598,20 @@
       "media_galleries/media_galleries_test_util.h",
     ]
     deps += [
+      "//components/crx_file",
       "//components/drive:test_support",
+      "//components/safe_browsing/core/db:v4_test_util",
       "//components/services/unzip:in_process",
       "//components/storage_monitor:test_support",
       "//extensions:test_support",
+      "//extensions/browser",
+      "//extensions/browser:test_support",
       "//google_apis:test_support",
       "//services/data_decoder/public/cpp:test_support",
     ]
+    if (is_chromeos) {
+      deps += [ "//chrome/browser/chromeos" ]
+    }
   }
 
   if (enable_library_cdms) {
@@ -6429,6 +6640,11 @@
       "safe_browsing/mock_report_sender.cc",
       "safe_browsing/mock_report_sender.h",
     ]
+    deps += [
+      "//chrome/browser/safe_browsing",
+      "//components/encrypted_messages",
+      "//components/security_interstitials/content:security_interstitial_page",
+    ]
   }
 
   if (safe_browsing_mode == 1) {
@@ -6438,6 +6654,11 @@
       "safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.cc",
       "safe_browsing/cloud_content_scanning/fake_deep_scanning_dialog_delegate.h",
     ]
+    deps += [
+      "//chrome/browser/safe_browsing",
+      "//components/enterprise/common/proto:connectors_proto",
+      "//components/safe_browsing/core/db:test_database_manager",
+    ]
   }
 
   if (has_spellcheck_panel) {
@@ -6447,6 +6668,7 @@
       "spellchecker/test/spellcheck_panel_browsertest_helper.cc",
       "spellchecker/test/spellcheck_panel_browsertest_helper.h",
     ]
+    deps += [ "//components/spellcheck/common" ]
   }
 }
 
@@ -6459,16 +6681,8 @@
     visibility = [ "//chrome/test:test_support_ui" ]
 
     sources = [
-      "interstitials/security_interstitial_idn_test.cc",
-      "interstitials/security_interstitial_idn_test.h",
-      "password_manager/password_manager_test_base.cc",
-      "password_manager/password_manager_test_base.h",
       "signin/token_revoker_test_utils.cc",
       "signin/token_revoker_test_utils.h",
-      "ssl/cert_verifier_platform_browser_test.cc",
-      "ssl/cert_verifier_platform_browser_test.h",
-      "ui/webui/signin/login_ui_test_utils.cc",
-      "ui/webui/signin/login_ui_test_utils.h",
       "ui/webui/test_data_source.cc",
       "ui/webui/test_data_source.h",
       "ui/webui/web_ui_test_handler.cc",
@@ -6482,7 +6696,10 @@
       "//chrome/test/data:web_ui_test_bindings",
       "//components/metrics:test_support",
       "//components/password_manager/core/browser:test_support",
+      "//components/signin/public/identity_manager",
+      "//components/sync/driver:test_support",
       "//components/translate/content/common",
+      "//content/test:test_support",
     ]
 
     public_deps = [ "//net:test_support" ]
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 645778a..32808a3 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -287,9 +287,9 @@
 const FeatureEntry::Choice kTouchTextSelectionStrategyChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kTouchSelectionStrategyCharacter,
-     switches::kTouchTextSelectionStrategy, "character"},
+     blink::switches::kTouchTextSelectionStrategy, "character"},
     {flag_descriptions::kTouchSelectionStrategyDirection,
-     switches::kTouchTextSelectionStrategy, "direction"}};
+     blink::switches::kTouchTextSelectionStrategy, "direction"}};
 
 const FeatureEntry::Choice kTraceUploadURL[] = {
     {flags_ui::kGenericExperimentChoiceDisabled, "", ""},
@@ -306,9 +306,9 @@
 const FeatureEntry::Choice kPassiveListenersChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kPassiveEventListenerTrue,
-     switches::kPassiveListenersDefault, "true"},
+     blink::switches::kPassiveListenersDefault, "true"},
     {flag_descriptions::kPassiveEventListenerForceAllTrue,
-     switches::kPassiveListenersDefault, "forcealltrue"},
+     blink::switches::kPassiveListenersDefault, "forcealltrue"},
 };
 
 const FeatureEntry::Choice kDataReductionProxyServerExperiment[] = {
@@ -2909,7 +2909,7 @@
          "IPH_DemoMode")},
     {"disable-threaded-scrolling", flag_descriptions::kThreadedScrollingName,
      flag_descriptions::kThreadedScrollingDescription, kOsAll,
-     SINGLE_DISABLE_VALUE_TYPE(switches::kDisableThreadedScrolling)},
+     SINGLE_DISABLE_VALUE_TYPE(blink::switches::kDisableThreadedScrolling)},
     {"extension-content-verification",
      flag_descriptions::kExtensionContentVerificationName,
      flag_descriptions::kExtensionContentVerificationDescription, kOsDesktop,
@@ -3278,9 +3278,9 @@
      // features controlled by kBlinkSettings, we'll need to add logic to
      // merge the flag values.
      ENABLE_DISABLE_VALUE_TYPE_AND_VALUE(
-         switches::kBlinkSettings,
+         blink::switches::kBlinkSettings,
          "disallowFetchForDocWrittenScriptsInMainFrame=true",
-         switches::kBlinkSettings,
+         blink::switches::kBlinkSettings,
          "disallowFetchForDocWrittenScriptsInMainFrame=false")},
 #if defined(OS_WIN)
     {"use-winrt-midi-api", flag_descriptions::kUseWinrtMidiApiName,
@@ -4119,6 +4119,10 @@
      flag_descriptions::kTabGroupsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kTabGroups)},
 
+    {"tab-groups-auto-create", flag_descriptions::kTabGroupsAutoCreateName,
+     flag_descriptions::kTabGroupsAutoCreateDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kTabGroupsAutoCreate)},
+
     {"tab-groups-collapse", flag_descriptions::kTabGroupsCollapseName,
      flag_descriptions::kTabGroupsCollapseDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kTabGroupsCollapse)},
@@ -6459,6 +6463,13 @@
      FEATURE_VALUE_TYPE(ash::features::kTemporaryHoldingSpace)},
 #endif
 
+#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX) || \
+    defined(OS_CHROMEOS)
+    {"enable-oop-print-drivers", flag_descriptions::kEnableOopPrintDriversName,
+     flag_descriptions::kEnableOopPrintDriversDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(printing::features::kEnableOopPrintDrivers)},
+#endif
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index d14f38ef..2569646 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -4160,7 +4160,7 @@
   auto* find_helper =
       find_in_page::FindTabHelper::FromWebContents(embedder_web_contents);
   find_helper->StartFinding(base::ASCIIToUTF16("doesn't matter"), true, true,
-                            true);
+                            false);
   auto pending =
       content::GetRenderFrameHostsWithPendingFindResults(embedder_web_contents);
   // Request for main frame of the tab.
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index 84976d72..b5e06a21 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -152,7 +152,7 @@
   }
 
   // PrintPreviewUI::TestDelegate implementation.
-  void DidGetPreviewPageCount(int page_count) override {
+  void DidGetPreviewPageCount(uint32_t page_count) override {
     total_page_count_ = page_count;
   }
 
@@ -178,8 +178,8 @@
   gfx::Size dialog_size() { return dialog_size_; }
 
  private:
-  int total_page_count_ = 1;
-  int rendered_page_count_ = 0;
+  uint32_t total_page_count_ = 1;
+  uint32_t rendered_page_count_ = 0;
   base::RunLoop* run_loop_ = nullptr;
   gfx::Size dialog_size_;
 };
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 48ea2b1..fdf6ee9 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -135,7 +135,7 @@
 #include "chrome/browser/android/search_permissions/search_permissions_service.h"
 #include "chrome/browser/android/webapps/webapp_registry.h"
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
-#include "components/cdm/browser/media_drm_storage_impl.h"
+#include "components/cdm/browser/media_drm_storage_impl.h"  // nogncheck crbug.com/1125897
 #include "components/feed/buildflags.h"
 #include "components/feed/core/v2/public/feed_service.h"
 #include "components/feed/feed_feature_list.h"
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
index 4d41386..47e0891f 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
@@ -28,7 +28,7 @@
 #include "url/origin.h"
 
 #if defined(OS_ANDROID)
-#include "components/cdm/browser/media_drm_storage_impl.h"
+#include "components/cdm/browser/media_drm_storage_impl.h"  // nogncheck crbug.com/1125897
 #endif
 
 using content::BrowserThread;
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 5771450..3de6004 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -118,9 +118,6 @@
 #include "chrome/browser/ui/webui/downloads/downloads.mojom.h"
 #include "chrome/browser/ui/webui/downloads/downloads_ui.h"
 #include "chrome/browser/ui/webui/media/media_feeds_ui.h"
-#include "chrome/browser/ui/webui/nearby_share/nearby_share.mojom.h"
-#include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
-#include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h"
 #include "chrome/browser/ui/webui/read_later/read_later.mojom.h"
@@ -164,6 +161,9 @@
 #include "chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_dialog.h"
 #include "chrome/browser/ui/webui/chromeos/network_ui.h"
 #include "chrome/browser/ui/webui/internals/web_app/web_app_internals.mojom.h"
+#include "chrome/browser/ui/webui/nearby_share/nearby_share.mojom.h"
+#include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
+#include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"  // nogncheck crbug.com/1125897
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
 #include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom.h"
@@ -195,8 +195,8 @@
 #endif
 
 #if defined(OS_CHROMEOS) && !defined(OFFICIAL_BUILD)
-#include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom.h"
-#include "chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom.h"
+#include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom.h"  // nogncheck crbug.com/1125897
+#include "chromeos/components/telemetry_extension_ui/mojom/probe_service.mojom.h"  // nogncheck crbug.com/1125897
 #include "chromeos/components/telemetry_extension_ui/telemetry_extension_ui.h"
 #endif
 
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 346cc5c..eeb78fa 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -291,7 +291,7 @@
 
 #if BUILDFLAG(ENABLE_RLZ)
 #include "chrome/browser/rlz/chrome_rlz_tracker_delegate.h"
-#include "components/rlz/rlz_tracker.h"
+#include "components/rlz/rlz_tracker.h"  // nogncheck crbug.com/1125897
 #endif  // BUILDFLAG(ENABLE_RLZ)
 
 #if BUILDFLAG(IS_LACROS)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 735af5a..221a1007 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -115,8 +115,6 @@
 #include "chrome/browser/resource_coordinator/background_tab_navigation_throttle.h"
 #include "chrome/browser/safe_browsing/certificate_reporting_service.h"
 #include "chrome/browser/safe_browsing/certificate_reporting_service_factory.h"
-#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
-#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "chrome/browser/safe_browsing/delayed_warning_navigation_throttle.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_throttle.h"
@@ -473,7 +471,7 @@
 #include "chrome/browser/webauthn/authenticator_request_scheduler.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
 #include "chrome/common/importer/profile_import.mojom.h"
-#include "chrome/grit/chrome_unscaled_resources.h"
+#include "chrome/grit/chrome_unscaled_resources.h"  // nogncheck crbug.com/1125897
 #endif  //  !defined(OS_ANDROID)
 
 #if defined(OS_WIN) || defined(OS_MAC) || \
@@ -609,13 +607,18 @@
 #include "chrome/browser/safe_browsing/client_side_detection_service_factory.h"
 #endif
 
+#if BUILDFLAG(SAFE_BROWSING_DB_LOCAL)
+#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"  // nogncheck crbug.com/1125897
+#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"  // nogncheck crbug.com/1125897
+#endif
+
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
 #include "chrome/browser/offline_pages/offline_page_tab_helper.h"
 #include "chrome/browser/offline_pages/offline_page_url_loader_request_interceptor.h"
 #endif
 
 #if BUILDFLAG(ENABLE_VR) && !defined(OS_ANDROID)
-#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"  // nogncheck crbug.com/1125897
 #endif
 
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
@@ -2082,8 +2085,8 @@
     return;
   }
 
-  if (browser_command_line.HasSwitch(switches::kBlinkSettings) ||
-      command_line->HasSwitch(switches::kBlinkSettings)) {
+  if (browser_command_line.HasSwitch(blink::switches::kBlinkSettings) ||
+      command_line->HasSwitch(blink::switches::kBlinkSettings)) {
     // The field trials should be configured to force users that specify the
     // blink-settings flag into a group with no params, and we return
     // above if no params were specified, so it's an error if we reach
@@ -2093,7 +2096,7 @@
     return;
   }
 
-  command_line->AppendSwitchASCII(switches::kBlinkSettings,
+  command_line->AppendSwitchASCII(blink::switches::kBlinkSettings,
                                   base::JoinString(blink_settings, ","));
 }
 
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index 6c7a60f..786cf0b 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -48,6 +48,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "url/gurl.h"
 
@@ -476,7 +477,7 @@
   }
 
   void AppendBlinkSettingsSwitch(const char* value) {
-    command_line_.AppendSwitchASCII(switches::kBlinkSettings, value);
+    command_line_.AppendSwitchASCII(blink::switches::kBlinkSettings, value);
   }
 
  private:
@@ -494,13 +495,13 @@
 
 TEST_F(BlinkSettingsFieldTrialTest, NoFieldTrial) {
   AppendContentBrowserClientSwitches();
-  EXPECT_FALSE(command_line().HasSwitch(switches::kBlinkSettings));
+  EXPECT_FALSE(command_line().HasSwitch(blink::switches::kBlinkSettings));
 }
 
 TEST_F(BlinkSettingsFieldTrialTest, FieldTrialWithoutParams) {
   CreateFieldTrial(kDisallowFetchFieldTrialName, kFakeGroupName);
   AppendContentBrowserClientSwitches();
-  EXPECT_FALSE(command_line().HasSwitch(switches::kBlinkSettings));
+  EXPECT_FALSE(command_line().HasSwitch(blink::switches::kBlinkSettings));
 }
 
 TEST_F(BlinkSettingsFieldTrialTest, BlinkSettingsSwitchAlreadySpecified) {
@@ -508,18 +509,18 @@
   CreateFieldTrialWithParams(kDisallowFetchFieldTrialName, kFakeGroupName,
                              "key1", "value1", "key2", "value2");
   AppendContentBrowserClientSwitches();
-  EXPECT_TRUE(command_line().HasSwitch(switches::kBlinkSettings));
-  EXPECT_EQ("foo",
-            command_line().GetSwitchValueASCII(switches::kBlinkSettings));
+  EXPECT_TRUE(command_line().HasSwitch(blink::switches::kBlinkSettings));
+  EXPECT_EQ("foo", command_line().GetSwitchValueASCII(
+                       blink::switches::kBlinkSettings));
 }
 
 TEST_F(BlinkSettingsFieldTrialTest, FieldTrialEnabled) {
   CreateFieldTrialWithParams(kDisallowFetchFieldTrialName, kFakeGroupName,
                              "key1", "value1", "key2", "value2");
   AppendContentBrowserClientSwitches();
-  EXPECT_TRUE(command_line().HasSwitch(switches::kBlinkSettings));
-  EXPECT_EQ("key1=value1,key2=value2",
-            command_line().GetSwitchValueASCII(switches::kBlinkSettings));
+  EXPECT_TRUE(command_line().HasSwitch(blink::switches::kBlinkSettings));
+  EXPECT_EQ("key1=value1,key2=value2", command_line().GetSwitchValueASCII(
+                                           blink::switches::kBlinkSettings));
 }
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index e17bbb92..fa2f67c 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2961,6 +2961,14 @@
     "login/screens/recommend_apps/fake_recommend_apps_fetcher_delegate.h",
     "login/test/dialog_window_waiter.cc",
     "login/test/dialog_window_waiter.h",
+    "login/test/js_checker.cc",
+    "login/test/js_checker.h",
+    "login/test/oobe_auth_page_waiter.cc",
+    "login/test/oobe_auth_page_waiter.h",
+    "login/test/oobe_screen_exit_waiter.cc",
+    "login/test/oobe_screen_exit_waiter.h",
+    "login/test/oobe_screen_waiter.cc",
+    "login/test/oobe_screen_waiter.h",
     "login/test/scoped_help_app_for_test.cc",
     "login/test/scoped_help_app_for_test.h",
     "login/test/test_condition_waiter.h",
diff --git a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
index 8cd8a27..9673c65 100644
--- a/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
+++ b/chrome/browser/chromeos/arc/file_system_watcher/arc_file_system_watcher_service.cc
@@ -42,26 +42,27 @@
 
 namespace {
 
+// The storage path inside ARC container. This will be the path that is used in
+// MediaScanner.scanFile request.
+constexpr base::FilePath::CharType kAndroidStorageDir[] =
+    FILE_PATH_LITERAL("/storage");
+
 // The Downloads path inside ARC container. This will be the path that
 // is used in MediaScanner.scanFile request.
 constexpr base::FilePath::CharType kAndroidDownloadDir[] =
     FILE_PATH_LITERAL("/storage/emulated/0/Download");
 
+// TODO(crbug.com/929031): Move this to arc_volume_mounter_bridge.h.
 // The MyFiles path inside ARC container. This will be the path that is used in
-// MediaScanner.scanFile request. MediaScanner scans MyFiles under
-// /storage/MyFiles-read instead of /storage/MyFiles because non-system apps has
-// no access to /storage/MyFiles.
+// MediaScanner.scanFile request. UUID for the MyFiles volume is taken from
+// components/arc/volume_mounter/arc_volume_mounter_bridge.cc.
 constexpr base::FilePath::CharType kAndroidMyFilesDir[] =
-    FILE_PATH_LITERAL("/storage/MyFiles-read");
+    FILE_PATH_LITERAL("/storage/0000000000000000000000000000CAFEF00D2019");
 
 // The path for Downloads under MyFiles inside ARC container.
 constexpr base::FilePath::CharType kAndroidMyFilesDownloadsDir[] =
-    FILE_PATH_LITERAL("/storage/MyFiles-read/Downloads/");
-
-// The removable media path inside ARC container. This will be the path that
-// is used in MediaScanner.scanFile request.
-constexpr base::FilePath::CharType kAndroidRemovableMediaDir[] =
-    FILE_PATH_LITERAL("/storage");
+    FILE_PATH_LITERAL(
+        "/storage/0000000000000000000000000000CAFEF00D2019/Downloads/");
 
 // How long to wait for new inotify events before building the updated timestamp
 // map.
@@ -121,24 +122,9 @@
     if (!HasAndroidSupportedMediaExtension(cros_path))
       continue;
 
-    // TODO(b/163951541): Temporary hack, this will be changed when we change
-    // the path to UUID. The cros_dir for removable media is now changed to
-    // /media/removable/volume_name instead of just /media/removable.
-    // Meanwhile, the GetAndroidPath function accept the second parameter
-    // cros_dir as a way to identify if it is a removable media directory.
-    // Since the second parameter is only used for identification,
-    // and the identification happens through equality check with
-    // kCrosRemovableMediaDir string, it is safe to just pass
-    // kCrosRemovableMediaDir string directly when we know it is a removable
-    // media. In other word, the cros_removable_media_dir below is used as
-    // the ChromeOS analogue for the Android /storage directory.
-    base::FilePath cros_removable_media_dir =
-        base::FilePath(kCrosRemovableMediaDir);
-    base::FilePath android_path = GetAndroidPath(
-        cros_path,
-        cros_removable_media_dir.IsParent(cros_dir) ? cros_removable_media_dir
-                                                    : cros_dir,
-        android_dir);
+    base::FilePath android_path(android_dir);
+    cros_dir.AppendRelativePath(cros_path, &android_path);
+
     const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
     timestamp_map[android_path] = info.GetLastModifiedTime();
   }
@@ -453,13 +439,10 @@
 
   // Make sure the callback is triggered after the file system is attached in
   // file_task_runner.
-  // TODO(b/163951541): Temporary hack, this will be changed when we change the
-  // path to UUID. The kAndroidRemovableMediaDir is hardcoded here because
-  // CreateAndStartFileSystemWatcher accepts the removable media's volume's
-  // directory's parent in Android as second argument (i.e., /storage).
+  base::FilePath android_path =
+      base::FilePath(kAndroidStorageDir).Append(fs_uuid);
   removable_media_watchers_[fs_uuid] = CreateAndStartFileSystemWatcher(
-      base::FilePath(mount_path), base::FilePath(kAndroidRemovableMediaDir),
-      std::move(callback));
+      base::FilePath(mount_path), android_path, std::move(callback));
 }
 
 void ArcFileSystemWatcherService::StopWatchingRemovableMedia(
diff --git a/chrome/browser/chromeos/concierge_helper_service.cc b/chrome/browser/chromeos/concierge_helper_service.cc
index 38b98eb..8abed5d 100644
--- a/chrome/browser/chromeos/concierge_helper_service.cc
+++ b/chrome/browser/chromeos/concierge_helper_service.cc
@@ -18,13 +18,6 @@
 namespace chromeos {
 namespace {
 
-void OnStartConcierge(bool started) {
-  if (started)
-    VLOG(1) << "Concierge D-Bus service successfully started";
-  else
-    LOG(ERROR) << "Unable to start Concierge D-Bus service";
-}
-
 void OnSetVmCpuRestriction(
     base::Optional<vm_tools::concierge::SetVmCpuRestrictionResponse> response) {
   if (!response || !response->success()) {
@@ -33,18 +26,6 @@
   }
 }
 
-// Starts Concierge DBus service through debugd. If the service is already
-// running, this request will be ignored.
-void StartConcierge() {
-  auto* client = DBusThreadManager::Get()->GetDebugDaemonClient();
-  if (!client) {
-    LOG(WARNING) << "DebugDaemonClient is not available";
-    OnStartConcierge(false);
-    return;
-  }
-  client->StartConcierge(base::BindOnce(&OnStartConcierge));
-}
-
 // Adds a callback to be run when Concierge DBus service becomes available.
 // If the service is already available, runs the callback immediately.
 void WaitForConciergeToBeAvailable(
@@ -103,9 +84,7 @@
   return ConciergeHelperServiceFactory::GetForBrowserContext(context);
 }
 
-ConciergeHelperService::ConciergeHelperService() {
-  StartConcierge();
-}
+ConciergeHelperService::ConciergeHelperService() = default;
 
 void ConciergeHelperService::SetArcVmCpuRestriction(bool do_restrict) {
   MakeRestrictionRequest(vm_tools::concierge::CPU_CGROUP_ARCVM, do_restrict);
diff --git a/chrome/browser/chromeos/concierge_helper_service_unittest.cc b/chrome/browser/chromeos/concierge_helper_service_unittest.cc
index 9d5f2a9e..fac19b2e 100644
--- a/chrome/browser/chromeos/concierge_helper_service_unittest.cc
+++ b/chrome/browser/chromeos/concierge_helper_service_unittest.cc
@@ -11,7 +11,6 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/debug_daemon/fake_debug_daemon_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "dbus/bus.h"
@@ -20,24 +19,6 @@
 
 namespace chromeos {
 
-class TestDebugDaemonClient : public FakeDebugDaemonClient {
- public:
-  TestDebugDaemonClient() = default;
-  ~TestDebugDaemonClient() override = default;
-
-  void StartConcierge(ConciergeCallback callback) override {
-    ++start_concierge_called_;
-    FakeDebugDaemonClient::StartConcierge(std::move(callback));
-  }
-
-  size_t start_concierge_called() const { return start_concierge_called_; }
-
- private:
-  size_t start_concierge_called_{0};
-
-  DISALLOW_COPY_AND_ASSIGN(TestDebugDaemonClient);
-};
-
 class TestConciergeClient : public FakeConciergeClient {
  public:
   TestConciergeClient() = default;
@@ -96,7 +77,6 @@
   // testing::Test:
   void SetUp() override {
     auto setter = DBusThreadManager::GetSetterForTesting();
-    setter->SetDebugDaemonClient(std::make_unique<TestDebugDaemonClient>());
     setter->SetConciergeClient(std::make_unique<TestConciergeClient>());
     service_ = ConciergeHelperService::GetForBrowserContext(&profile_);
   }
@@ -111,11 +91,6 @@
         DBusThreadManager::Get()->GetConciergeClient());
   }
 
-  TestDebugDaemonClient* fake_debug_client() {
-    return static_cast<TestDebugDaemonClient*>(
-        DBusThreadManager::Get()->GetDebugDaemonClient());
-  }
-
   content::BrowserTaskEnvironment* task_environment() {
     return &task_environment_;
   }
@@ -128,12 +103,6 @@
   DISALLOW_COPY_AND_ASSIGN(ConciergeHelperServiceTest);
 };
 
-// Tests that ConciergeHelperService can be constructed and destructed. Also,
-// check that ConciergeHelperService starts Concierge on construction.
-TEST_F(ConciergeHelperServiceTest, TestConstructDestruct) {
-  EXPECT_EQ(1U, fake_debug_client()->start_concierge_called());
-}
-
 // Tests that ConciergeHelperService makes cpu restriction requests correctly.
 TEST_F(ConciergeHelperServiceTest, TestSetVmCpuRestriction) {
   vm_tools::concierge::SetVmCpuRestrictionResponse response;
diff --git a/chrome/browser/chromeos/crostini/crostini_disk.cc b/chrome/browser/chromeos/crostini/crostini_disk.cc
index 225174f..8ea4a8c 100644
--- a/chrome/browser/chromeos/crostini/crostini_disk.cc
+++ b/chrome/browser/chromeos/crostini/crostini_disk.cc
@@ -73,26 +73,15 @@
         base::BindOnce(&OnAmountOfFreeDiskSpace, std::move(callback), profile,
                        std::move(vm_name)));
   } else {
-    VLOG(1) << "Starting concierge";
     // Since we only care about the disk's current size and whether it's a
     // sparse disk, we claim there's plenty of free space available to prevent
     // error conditions in |OnCrostiniSufficientlyRunning|.
     constexpr int64_t kFakeAvailableDiskBytes =
         kDiskHeadroomBytes + kRecommendedDiskSizeBytes;
 
-    CrostiniManager::GetForProfile(profile)->StartConcierge(base::BindOnce(
-        [](OnceDiskInfoCallback callback, Profile* profile, std::string vm_name,
-           bool success) {
-          if (!success) {
-            LOG(ERROR) << "Failed to start concierge";
-            std::move(callback).Run(nullptr);
-            return;
-          }
-          OnCrostiniSufficientlyRunning(
-              std::move(callback), profile, std::move(vm_name),
-              kFakeAvailableDiskBytes, CrostiniResult::SUCCESS);
-        },
-        std::move(callback), profile, std::move(vm_name)));
+    OnCrostiniSufficientlyRunning(std::move(callback), profile,
+                                  std::move(vm_name), kFakeAvailableDiskBytes,
+                                  CrostiniResult::SUCCESS);
   }
 }
 
diff --git a/chrome/browser/chromeos/crostini/crostini_installer.cc b/chrome/browser/chromeos/crostini/crostini_installer.cc
index 8de0de66..9faad08 100644
--- a/chrome/browser/chromeos/crostini/crostini_installer.cc
+++ b/chrome/browser/chromeos/crostini/crostini_installer.cc
@@ -113,8 +113,6 @@
       return SetupResult::kSuccess;
     case InstallerError::kErrorLoadingTermina:
       return SetupResult::kErrorLoadingTermina;
-    case InstallerError::kErrorStartingConcierge:
-      return SetupResult::kErrorStartingConcierge;
     case InstallerError::kErrorCreatingDiskImage:
       return SetupResult::kErrorCreatingDiskImage;
     case InstallerError::kErrorStartingTermina:
@@ -149,8 +147,6 @@
       return SetupResult::kUserCancelledStart;
     case InstallerState::kInstallImageLoader:
       return SetupResult::kUserCancelledInstallImageLoader;
-    case InstallerState::kStartConcierge:
-      return SetupResult::kUserCancelledStartConcierge;
     case InstallerState::kCreateDiskImage:
       return SetupResult::kUserCancelledCreateDiskImage;
     case InstallerState::kStartTerminaVm:
@@ -326,15 +322,6 @@
     }
     return;
   }
-  UpdateInstallingState(InstallerState::kStartConcierge);
-}
-
-void CrostiniInstaller::OnConciergeStarted(bool success) {
-  DCHECK_EQ(installing_state_, InstallerState::kStartConcierge);
-  if (!success) {
-    HandleError(InstallerError::kErrorStartingConcierge);
-    return;
-  }
   UpdateInstallingState(InstallerState::kCreateDiskImage);
 }
 
@@ -499,12 +486,8 @@
       state_end_mark = 0.20;
       state_max_time = base::TimeDelta::FromSeconds(30);
       break;
-    case InstallerState::kStartConcierge:
-      state_start_mark = 0.20;
-      state_end_mark = 0.21;
-      break;
     case InstallerState::kCreateDiskImage:
-      state_start_mark = 0.21;
+      state_start_mark = 0.20;
       state_end_mark = 0.22;
       break;
     case InstallerState::kStartTerminaVm:
diff --git a/chrome/browser/chromeos/crostini/crostini_installer.h b/chrome/browser/chromeos/crostini/crostini_installer.h
index ce26ed5..ce38ef5 100644
--- a/chrome/browser/chromeos/crostini/crostini_installer.h
+++ b/chrome/browser/chromeos/crostini/crostini_installer.h
@@ -37,7 +37,7 @@
     // kUserCancelled = 1,
     kSuccess = 2,
     kErrorLoadingTermina = 3,
-    kErrorStartingConcierge = 4,
+    // kErrorStartingConcierge = 4,
     kErrorCreatingDiskImage = 5,
     kErrorStartingTermina = 6,
     kErrorStartingContainer = 7,
@@ -48,7 +48,7 @@
 
     kUserCancelledStart = 12,
     kUserCancelledInstallImageLoader = 13,
-    kUserCancelledStartConcierge = 14,
+    // kUserCancelledStartConcierge = 14,
     kUserCancelledCreateDiskImage = 15,
     kUserCancelledStartTerminaVm = 16,
     kUserCancelledCreateContainer = 17,
@@ -88,7 +88,6 @@
   // CrostiniManager::RestartObserver:
   void OnStageStarted(crostini::mojom::InstallerState stage) override;
   void OnComponentLoaded(crostini::CrostiniResult result) override;
-  void OnConciergeStarted(bool success) override;
   void OnDiskImageCreated(bool success,
                           vm_tools::concierge::DiskImageStatus status,
                           int64_t disk_size_available) override;
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 903c98f..7a7fce0 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -333,23 +333,6 @@
     }
     // Set the pref here, after we first successfully install something
     profile_->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
-    StartStage(mojom::InstallerState::kStartConcierge);
-    crostini_manager_->StartConcierge(base::BindOnce(
-        &CrostiniRestarter::ConciergeStarted, weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void ConciergeStarted(bool is_started) {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    for (auto& observer : observer_list_) {
-      observer.OnConciergeStarted(is_started);
-    }
-    if (ReturnEarlyIfAborted()) {
-      return;
-    }
-    if (!is_started) {
-      FinishRestart(CrostiniResult::CONCIERGE_START_FAILED);
-      return;
-    }
 
     // Allow concierge to choose an appropriate disk image size.
     int64_t disk_size_bytes = options_.disk_size_bytes.value_or(0);
@@ -1044,34 +1027,6 @@
   termina_installer_.Uninstall(std::move(callback));
 }
 
-void CrostiniManager::StartConcierge(BoolCallback callback) {
-  VLOG(1) << "Starting Concierge service";
-  chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->StartConcierge(
-      base::BindOnce(&CrostiniManager::OnStartConcierge,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void CrostiniManager::OnStartConcierge(BoolCallback callback, bool success) {
-  if (!success) {
-    LOG(ERROR) << "Failed to start Concierge service";
-    std::move(callback).Run(success);
-    return;
-  }
-  VLOG(1) << "Concierge service started";
-  VLOG(1) << "Waiting for Cicerone to announce availability.";
-
-  GetCiceroneClient()->WaitForServiceToBeAvailable(std::move(callback));
-}
-
-void CrostiniManager::OnStopConcierge(BoolCallback callback, bool success) {
-  if (!success) {
-    LOG(ERROR) << "Failed to stop Concierge service";
-  } else {
-    VLOG(1) << "Concierge service stopped";
-  }
-  std::move(callback).Run(success);
-}
-
 void CrostiniManager::CreateDiskImage(
     const base::FilePath& disk_path,
     vm_tools::concierge::StorageLocation storage_location,
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 459f9c8..1ff400d 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -189,7 +189,6 @@
     virtual ~RestartObserver() {}
     virtual void OnStageStarted(mojom::InstallerState stage) {}
     virtual void OnComponentLoaded(CrostiniResult result) {}
-    virtual void OnConciergeStarted(bool success) {}
     virtual void OnDiskImageCreated(bool success,
                                     vm_tools::concierge::DiskImageStatus status,
                                     int64_t disk_size_bytes) {}
@@ -243,10 +242,6 @@
   // Unloads and removes termina.
   void UninstallTermina(BoolCallback callback);
 
-  // Starts the Concierge service. |callback| is called after the method call
-  // finishes.
-  void StartConcierge(BoolCallback callback);
-
   // Checks the arguments for creating a new Termina VM disk image. Creates a
   // disk image for a Termina VM via ConciergeClient::CreateDiskImage.
   // |callback| is called if the arguments are bad, or after the method call
@@ -703,14 +698,6 @@
       base::Optional<vm_tools::concierge::GetVmEnterpriseReportingInfoResponse>
           response);
 
-  // Callback for CrostiniClient::StartConcierge. Called after the
-  // DebugDaemon service method finishes.
-  void OnStartConcierge(BoolCallback callback, bool success);
-
-  // Callback for CrostiniClient::StopConcierge. Called after the
-  // DebugDaemon service method finishes.
-  void OnStopConcierge(BoolCallback callback, bool success);
-
   // Callback for CiceroneClient::StartLxd. May indicate that LXD is still being
   // started in which case we will wait for OnStartLxdProgress events.
   void OnStartLxd(
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index c80358bec..d694b35 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -645,12 +645,6 @@
     }
   }
 
-  void OnConciergeStarted(bool success) override {
-    if (abort_on_concierge_started_) {
-      Abort();
-    }
-  }
-
   void OnDiskImageCreated(bool success,
                           vm_tools::concierge::DiskImageStatus status,
                           int64_t disk_size_available) override {
@@ -753,7 +747,6 @@
   const CrostiniManager::RestartId uninitialized_id_ =
       CrostiniManager::kUninitializedRestartId;
   bool abort_on_component_loaded_ = false;
-  bool abort_on_concierge_started_ = false;
   bool abort_on_disk_image_created_ = false;
   bool abort_on_vm_started_ = false;
   bool abort_on_container_created_ = false;
@@ -864,21 +857,6 @@
   ExpectRestarterUmaCount(1);
 }
 
-TEST_F(CrostiniManagerRestartTest, AbortOnConciergeStarted) {
-  abort_on_concierge_started_ = true;
-  restart_id_ = crostini_manager()->RestartCrostini(
-      container_id(),
-      base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()),
-      this);
-  run_loop()->Run();
-  EXPECT_FALSE(fake_concierge_client_->create_disk_image_called());
-  EXPECT_FALSE(fake_concierge_client_->start_termina_vm_called());
-  EXPECT_FALSE(fake_concierge_client_->get_container_ssh_keys_called());
-  ExpectCrostiniRestartResult(CrostiniResult::RESTART_ABORTED);
-  ExpectRestarterUmaCount(1);
-}
-
 TEST_F(CrostiniManagerRestartTest, AbortOnDiskImageCreated) {
   abort_on_disk_image_created_ = true;
   restart_id_ = crostini_manager()->RestartCrostini(
diff --git a/chrome/browser/chromeos/crostini/crostini_remover.cc b/chrome/browser/chromeos/crostini/crostini_remover.cc
index e3133ab..ed92fb8 100644
--- a/chrome/browser/chromeos/crostini/crostini_remover.cc
+++ b/chrome/browser/chromeos/crostini/crostini_remover.cc
@@ -34,15 +34,6 @@
 
 void CrostiniRemover::RemoveCrostini() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  CrostiniManager::GetForProfile(profile_)->StartConcierge(
-      base::BindOnce(&CrostiniRemover::OnConciergeStarted, this));
-}
-
-void CrostiniRemover::OnConciergeStarted(bool is_successful) {
-  if (!is_successful) {
-    std::move(callback_).Run(CrostiniResult::UNKNOWN_ERROR);
-    return;
-  }
   CrostiniManager::GetForProfile(profile_)->StopVm(
       vm_name_, base::BindOnce(&CrostiniRemover::StopVmFinished, this));
 }
diff --git a/chrome/browser/chromeos/crostini/crostini_remover.h b/chrome/browser/chromeos/crostini/crostini_remover.h
index afc4332..89c92c5 100644
--- a/chrome/browser/chromeos/crostini/crostini_remover.h
+++ b/chrome/browser/chromeos/crostini/crostini_remover.h
@@ -22,7 +22,6 @@
 
   ~CrostiniRemover();
 
-  void OnConciergeStarted(bool is_successful);
   void StopVmFinished(crostini::CrostiniResult result);
   void DestroyDiskImageFinished(bool success);
   void UninstallTerminaFinished(bool success);
diff --git a/chrome/browser/chromeos/crostini/crostini_types.mojom b/chrome/browser/chromeos/crostini/crostini_types.mojom
index 65efb5c..56420cda 100644
--- a/chrome/browser/chromeos/crostini/crostini_types.mojom
+++ b/chrome/browser/chromeos/crostini/crostini_types.mojom
@@ -7,7 +7,6 @@
 enum InstallerState {
   kStart,               // Just started installation
   kInstallImageLoader,  // Loading the Termina VM component.
-  kStartConcierge,      // Starting the Concierge D-Bus client.
   kCreateDiskImage,     // Creating the image for the Termina VM.
   kStartTerminaVm,      // Starting the Termina VM.
   kCreateContainer,     // Creating the container inside the Termina VM.
@@ -21,7 +20,6 @@
 enum InstallerError {
   kNone,
   kErrorLoadingTermina,
-  kErrorStartingConcierge,
   kErrorCreatingDiskImage,
   kErrorStartingTermina,
   kErrorStartingContainer,
diff --git a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
index 1cd1c67..baa5942 100644
--- a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
+++ b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
@@ -164,7 +164,14 @@
 
 bool FakeDiskMountManager::AddMountPointForTest(
     const MountPointInfo& mount_point) {
-  return false;
+  if (mount_point.mount_type == chromeos::MOUNT_TYPE_DEVICE &&
+      disks_.find(mount_point.source_path) == disks_.end()) {
+    // Device mount point must have a disk entry.
+    return false;
+  }
+
+  mount_points_.insert(std::make_pair(mount_point.mount_path, mount_point));
+  return true;
 }
 
 void FakeDiskMountManager::InvokeDiskEventForTest(
diff --git a/chrome/browser/chromeos/file_manager/path_util.cc b/chrome/browser/chromeos/file_manager/path_util.cc
index eac3c98..5877feb 100644
--- a/chrome/browser/chromeos/file_manager/path_util.cc
+++ b/chrome/browser/chromeos/file_manager/path_util.cc
@@ -33,6 +33,8 @@
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/disks/disk.h"
+#include "chromeos/disks/disk_mount_manager.h"
 #include "components/arc/arc_util.h"
 #include "components/drive/file_system_core_util.h"
 #include "components/user_manager/user.h"
@@ -70,9 +72,13 @@
     FILE_PATH_LITERAL("/external_files");
 // Sync with the volume provider in ARC++ side.
 constexpr char kArcRemovableMediaContentUrlPrefix[] =
-    "content://org.chromium.arc.volumeprovider/removable/";
+    "content://org.chromium.arc.volumeprovider/";
+// The dummy UUID of the MyFiles volume is taken from
+// components/arc/volume_mounter/arc_volume_mounter_bridge.cc.
+// TODO(crbug.com/929031): Move MyFiles constants to a common place.
 constexpr char kArcMyFilesContentUrlPrefix[] =
-    "content://org.chromium.arc.volumeprovider/MyFiles/";
+    "content://org.chromium.arc.volumeprovider/"
+    "0000000000000000000000000000CAFEF00D2019/";
 constexpr char kArcDriveContentUrlPrefix[] =
     "content://org.chromium.arc.volumeprovider/MyDrive/";
 
@@ -140,6 +146,51 @@
   return drive_path;
 }
 
+// Extracts the volume name of a removable device. |relative_path| is expected
+// to be of the form <volume name>/..., which is relative to /media/removable.
+std::string ExtractVolumeNameFromRelativePathForRemovableMedia(
+    const base::FilePath& relative_path) {
+  std::vector<base::FilePath::StringType> components;
+  relative_path.GetComponents(&components);
+  if (components.empty()) {
+    LOG(WARNING) << "Failed to extract volume name from relative path: "
+                 << relative_path;
+    return std::string();
+  }
+  return components[0];
+}
+
+// Returns the source path of a removable device using its volume name as a key.
+// An empty string is returned when it fails to get a valid mount point from
+// DiskMountManager.
+std::string GetSourcePathForRemovableMedia(const std::string& volume_name) {
+  const std::string mount_path(
+      base::StringPrintf("%s/%s", kRemovableMediaPath, volume_name.c_str()));
+  const auto& mount_points =
+      chromeos::disks::DiskMountManager::GetInstance()->mount_points();
+  const auto found = mount_points.find(mount_path);
+  return found == mount_points.end() ? std::string()
+                                     : found->second.source_path;
+}
+
+// Returns the UUID of a removable device using its volume name as a key.
+// An empty string is returned when it fails to get valid source path and disk
+// from DiskMountManager.
+std::string GetFsUuidForRemovableMedia(const std::string& volume_name) {
+  const std::string source_path = GetSourcePathForRemovableMedia(volume_name);
+  if (source_path.empty()) {
+    LOG(WARNING) << "No source path is found for volume name: " << volume_name;
+    return std::string();
+  }
+  const chromeos::disks::Disk* disk =
+      chromeos::disks::DiskMountManager::GetInstance()->FindDiskBySourcePath(
+          source_path);
+  std::string fs_uuid = disk == nullptr ? std::string() : disk->fs_uuid();
+  if (fs_uuid.empty())
+    LOG(WARNING) << "No UUID is found for volume name: " << volume_name;
+  return fs_uuid;
+}
+
 }  // namespace
 
 const base::FilePath::CharType kRemovableMediaPath[] =
@@ -422,12 +473,29 @@
   base::FilePath relative_path;
   if (base::FilePath(kRemovableMediaPath)
           .AppendRelativePath(path, &relative_path)) {
-    *arc_url_out = GURL(kArcRemovableMediaContentUrlPrefix)
-                       .Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
+    const std::string volume_name =
+        ExtractVolumeNameFromRelativePathForRemovableMedia(relative_path);
+    if (volume_name.empty())
+      return false;
+    const std::string fs_uuid = GetFsUuidForRemovableMedia(volume_name);
+    if (fs_uuid.empty())
+      return false;
+    // Replace the volume name in the relative path with the UUID.
+    base::FilePath relative_path_with_uuid = base::FilePath(fs_uuid);
+    if (!base::FilePath(volume_name)
+             .AppendRelativePath(relative_path, &relative_path_with_uuid)) {
+      LOG(WARNING) << "Failed to replace volume name \"" << volume_name
+                   << "\" in relative path \"" << relative_path
+                   << "\" with UUID \"" << fs_uuid << "\"";
+      return false;
+    }
+    *arc_url_out =
+        GURL(kArcRemovableMediaContentUrlPrefix)
+            .Resolve(net::EscapePath(relative_path_with_uuid.AsUTF8Unsafe()));
     return true;
   }
 
-  // Convert paths under MyFiles
+  // Convert paths under MyFiles.
   if (base::FilePath(GetMyFilesFolderForProfile(primary_profile))
           .AppendRelativePath(path, &relative_path)) {
     *arc_url_out = GURL(kArcMyFilesContentUrlPrefix)
@@ -463,6 +531,12 @@
     force_external = true;
   }
 
+  // Force external URL for files under /media/archive.
+  if (base::FilePath(kArchiveMountPath)
+          .AppendRelativePath(path, &relative_path)) {
+    force_external = true;
+  }
+
   // Force external URL for smbfs.
   chromeos::smb_client::SmbService* smb_service =
       chromeos::smb_client::SmbServiceFactory::Get(primary_profile);
diff --git a/chrome/browser/chromeos/file_manager/path_util_unittest.cc b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
index b3ff2593..1c9c211 100644
--- a/chrome/browser/chromeos/file_manager/path_util_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
@@ -29,6 +29,7 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/disks/disk.h"
 #include "components/account_id/account_id.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
@@ -597,6 +598,23 @@
                                      storage::FileSystemMountOption(),
                                      crostini_mount_point_);
 
+    chromeos::disks::DiskMountManager::InitializeForTesting(
+        new FakeDiskMountManager);
+
+    // Add the disk and mount point for a fake removable device.
+    ASSERT_TRUE(
+        chromeos::disks::DiskMountManager::GetInstance()->AddDiskForTest(
+            chromeos::disks::Disk::Builder()
+                .SetDevicePath("/device/source_path")
+                .SetFileSystemUUID("0123-abcd")
+                .Build()));
+    ASSERT_TRUE(
+        chromeos::disks::DiskMountManager::GetInstance()->AddMountPointForTest(
+            chromeos::disks::DiskMountManager::MountPointInfo(
+                "/device/source_path", "/media/removable/a",
+                chromeos::MOUNT_TYPE_DEVICE,
+                chromeos::disks::MOUNT_CONDITION_NONE)));
+
     // Run pending async tasks resulting from profile construction to ensure
     // these are complete before the test begins.
     base::RunLoop().RunUntilIdle();
@@ -610,6 +628,8 @@
 
     // Run all pending tasks before destroying testing profile.
     base::RunLoop().RunUntilIdle();
+
+    chromeos::disks::DiskMountManager::Shutdown();
   }
 
  protected:
@@ -634,7 +654,7 @@
   GURL url;
   EXPECT_TRUE(ConvertPathToArcUrl(
       base::FilePath::FromUTF8Unsafe("/media/removable/a/b/c"), &url));
-  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/removable/a/b/c"),
+  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/0123-abcd/b/c"),
             url);
 }
 
@@ -646,7 +666,9 @@
       chromeos::ProfileHelper::Get()->GetProfileByUserIdHashForTest(
           "user@gmail.com-hash"));
   EXPECT_TRUE(ConvertPathToArcUrl(myfiles.AppendASCII("a/b/c"), &url));
-  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/MyFiles/a/b/c"),
+  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
+                 "0000000000000000000000000000CAFEF00D2019/"
+                 "a/b/c"),
             url);
 }
 
@@ -729,9 +751,9 @@
           [](base::RunLoop* run_loop, const std::vector<GURL>& urls) {
             run_loop->Quit();
             ASSERT_EQ(1U, urls.size());
-            EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
-                           "removable/a/b/c"),
-                      urls[0]);
+            EXPECT_EQ(
+                GURL("content://org.chromium.arc.volumeprovider/0123-abcd/b/c"),
+                urls[0]);
           },
           &run_loop));
   run_loop.Run();
@@ -751,9 +773,10 @@
           [](base::RunLoop* run_loop, const std::vector<GURL>& urls) {
             run_loop->Quit();
             ASSERT_EQ(1U, urls.size());
-            EXPECT_EQ(
-                GURL("content://org.chromium.arc.volumeprovider/MyFiles/a/b/c"),
-                urls[0]);
+            EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
+                           "0000000000000000000000000000CAFEF00D2019/"
+                           "a/b/c"),
+                      urls[0]);
           },
           &run_loop));
   run_loop.Run();
@@ -935,9 +958,9 @@
             run_loop->Quit();
             ASSERT_EQ(4U, urls.size());
             EXPECT_EQ(GURL(), urls[0]);  // Invalid URL.
-            EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
-                           "removable/a/b/c"),
-                      urls[1]);
+            EXPECT_EQ(
+                GURL("content://org.chromium.arc.volumeprovider/0123-abcd/b/c"),
+                urls[1]);
             EXPECT_EQ(GURL("content://org.chromium.arc.chromecontentprovider/"
                            "externalfile%3Adrivefs-b1f44746e7144c3caafeacaa8bb5"
                            "c569%2Fa%2Fb%2Fc"),
diff --git a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
index dc6bed5..9a09c0b 100644
--- a/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/chromeos/input_method/input_method_manager_impl.cc
@@ -1145,7 +1145,7 @@
             unfiltered_input_method_id)) {
       // Legacy IMEs or xkb layouts are alwayes active.
       state->active_input_method_ids.push_back(unfiltered_input_method_id);
-    } else if (component_extension_ime_manager_->IsWhitelisted(
+    } else if (component_extension_ime_manager_->IsAllowlisted(
                    unfiltered_input_method_id)) {
       if (enable_extension_loading_) {
         component_extension_ime_manager_->LoadComponentExtensionIME(
diff --git a/chrome/browser/chromeos/input_method/native_input_method_engine_browsertest.cc b/chrome/browser/chromeos/input_method/native_input_method_engine_browsertest.cc
index 312ffe2..9f287e1e 100644
--- a/chrome/browser/chromeos/input_method/native_input_method_engine_browsertest.cc
+++ b/chrome/browser/chromeos/input_method/native_input_method_engine_browsertest.cc
@@ -142,6 +142,8 @@
   NativeInputMethodEngineTest() : input_method_(this) {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/{chromeos::features::kAssistPersonalInfo,
+                              chromeos::features::kAssistPersonalInfoEmail,
+                              chromeos::features::kAssistPersonalInfoName,
                               chromeos::features::kEmojiSuggestAddition},
         /*disabled_features=*/{});
   }
@@ -639,7 +641,7 @@
  public:
   NativeInputMethodEngineAssistiveOff() {
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{},
+        /*enabled_features=*/{chromeos::features::kAssistPersonalInfoName},
         /*disabled_features=*/{chromeos::features::kAssistPersonalInfo,
                                chromeos::features::kEmojiSuggestAddition});
   }
diff --git a/chrome/browser/chromeos/input_method/personal_info_suggester.cc b/chrome/browser/chromeos/input_method/personal_info_suggester.cc
index cc1673d..a5e65b8 100644
--- a/chrome/browser/chromeos/input_method/personal_info_suggester.cc
+++ b/chrome/browser/chromeos/input_method/personal_info_suggester.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/extensions/api/input_ime/input_ime_api.h"
 
 #include "ash/public/cpp/ash_pref_names.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_pref_names.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
@@ -133,36 +135,54 @@
   if (!(RE2::FullMatch(lower_case_utf8_text, ".* $"))) {
     return AssistiveType::kGenericAction;
   }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
-                                        kEmailRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalEmail;
+
+  if (base::FeatureList::IsEnabled(
+          chromeos::features::kAssistPersonalInfoAddress)) {
+    if (RE2::FullMatch(
+            lower_case_utf8_text,
+            base::StringPrintf(".*%s%s%s", kSingleOrPluralSubjectRegex,
+                               kAddressRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalAddress;
+    }
   }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
-                                        kNameRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalName;
+
+  if (base::FeatureList::IsEnabled(
+          chromeos::features::kAssistPersonalInfoEmail)) {
+    if (RE2::FullMatch(lower_case_utf8_text,
+                       base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
+                                          kEmailRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalEmail;
+    }
   }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleOrPluralSubjectRegex,
-                                        kAddressRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalAddress;
+
+  if (base::FeatureList::IsEnabled(
+          chromeos::features::kAssistPersonalInfoName)) {
+    if (RE2::FullMatch(lower_case_utf8_text,
+                       base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
+                                          kNameRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalName;
+    }
+    if (RE2::FullMatch(lower_case_utf8_text,
+                       base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
+                                          kFirstNameRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalFirstName;
+    }
+    if (RE2::FullMatch(lower_case_utf8_text,
+                       base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
+                                          kLastNameRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalLastName;
+    }
   }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
-                                        kPhoneNumberRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalPhoneNumber;
+
+  if (base::FeatureList::IsEnabled(
+          chromeos::features::kAssistPersonalInfoPhoneNumber)) {
+    if (RE2::FullMatch(lower_case_utf8_text,
+                       base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
+                                          kPhoneNumberRegex, kTriggersRegex))) {
+      return AssistiveType::kPersonalPhoneNumber;
+    }
   }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
-                                        kFirstNameRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalFirstName;
-  }
-  if (RE2::FullMatch(lower_case_utf8_text,
-                     base::StringPrintf(".*%s%s%s", kSingleSubjectRegex,
-                                        kLastNameRegex, kTriggersRegex))) {
-    return AssistiveType::kPersonalLastName;
-  }
+
   return AssistiveType::kGenericAction;
 }
 
diff --git a/chrome/browser/chromeos/input_method/personal_info_suggester_unittest.cc b/chrome/browser/chromeos/input_method/personal_info_suggester_unittest.cc
index 2052552..38b4f0a 100644
--- a/chrome/browser/chromeos/input_method/personal_info_suggester_unittest.cc
+++ b/chrome/browser/chromeos/input_method/personal_info_suggester_unittest.cc
@@ -8,9 +8,11 @@
 #include "base/guid.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_pref_names.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
@@ -191,6 +193,11 @@
 };
 
 TEST_F(PersonalInfoSuggesterTest, SuggestEmail) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
   suggester_->Suggest(base::UTF8ToUTF16("my email is "));
@@ -205,7 +212,24 @@
   suggestion_handler_->VerifySuggestion(email_, 0);
 }
 
-TEST_F(PersonalInfoSuggesterTest, DoNotSuggestEmail) {
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestEmailWhenFlagIsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{chromeos::features::kAssistPersonalInfoEmail});
+
+  profile_->set_profile_name(base::UTF16ToUTF8(email_));
+
+  suggester_->Suggest(base::UTF8ToUTF16("my email is "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+}
+
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestEmailWhenPrefixDoesNotMatch) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
   suggester_->Suggest(base::UTF8ToUTF16("my email is John"));
@@ -216,6 +240,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, DoNotSuggestWhenVirtualKeyboardEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
@@ -225,6 +254,11 @@
 
 TEST_F(PersonalInfoSuggesterTest,
        SendsEmailSuggestionToExtensionWhenVirtualKeyboardEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
@@ -234,6 +268,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, SuggestNames) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoName},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FIRST,
@@ -258,7 +297,36 @@
   suggestion_handler_->VerifySuggestion(full_name_, 0);
 }
 
-TEST_F(PersonalInfoSuggesterTest, DoNotSuggestNames) {
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestNamesWhenFlagIsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{chromeos::features::kAssistPersonalInfoName});
+
+  autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
+                                             autofill::test::kEmptyOrigin);
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FIRST,
+                              first_name_);
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_LAST, last_name_);
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
+  personal_data_->AddProfile(autofill_profile);
+
+  suggester_->Suggest(base::UTF8ToUTF16("my first name is "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+
+  suggester_->Suggest(base::UTF8ToUTF16("my last name is: "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+
+  suggester_->Suggest(base::UTF8ToUTF16("my name is "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+}
+
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestNamesWhenPrefixDoesNotMatch) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FIRST,
@@ -281,6 +349,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, SuggestAddress) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoAddress},
+      /*disabled_features=*/{});
+
   autofill::CountryNames::SetLocaleString("en-US");
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
@@ -316,7 +389,37 @@
   suggestion_handler_->VerifySuggestion(address_, 0);
 }
 
-TEST_F(PersonalInfoSuggesterTest, DoNotSuggestAddress) {
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestAddressWhenFlagIsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{chromeos::features::kAssistPersonalInfoAddress});
+
+  autofill::CountryNames::SetLocaleString("en-US");
+  autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
+                                             autofill::test::kEmptyOrigin);
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_LINE1,
+                              base::UTF8ToUTF16("1 Dream Road"));
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_CITY,
+                              base::UTF8ToUTF16("Hollywood"));
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_ZIP,
+                              base::UTF8ToUTF16("12345"));
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_STATE,
+                              base::UTF8ToUTF16("CA"));
+  autofill_profile.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_COUNTRY,
+                              base::UTF8ToUTF16("US"));
+  personal_data_->AddProfile(autofill_profile);
+
+  suggester_->Suggest(base::UTF8ToUTF16("my address is "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+}
+
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestAddressWhenPrefixDoesNotMatch) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoAddress},
+      /*disabled_features=*/{});
+
   autofill::CountryNames::SetLocaleString("en-US");
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
@@ -343,6 +446,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, SuggestPhoneNumber) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoPhoneNumber},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(
@@ -369,7 +477,30 @@
   suggestion_handler_->VerifySuggestion(phone_number_, 0);
 }
 
-TEST_F(PersonalInfoSuggesterTest, DoNotSuggestPhoneNumber) {
+TEST_F(PersonalInfoSuggesterTest, DoNotSuggestPhoneNumberWhenFlagIsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{
+          chromeos::features::kAssistPersonalInfoPhoneNumber});
+
+  autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
+                                             autofill::test::kEmptyOrigin);
+  autofill_profile.SetRawInfo(
+      autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, phone_number_);
+  personal_data_->AddProfile(autofill_profile);
+
+  suggester_->Suggest(base::UTF8ToUTF16("my phone number is "));
+  suggestion_handler_->VerifySuggestion(base::EmptyString16(), 0);
+}
+
+TEST_F(PersonalInfoSuggesterTest,
+       DoNotSuggestPhoneNumberWhenPrefixDoesNotMatch) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoPhoneNumber},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(
@@ -390,6 +521,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, AcceptSuggestionWithDownEnter) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
 
   suggester_->Suggest(base::UTF8ToUTF16("my email is "));
@@ -401,6 +537,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, AcceptSuggestionWithUpEnter) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->SetIntKey(kPersonalInfoSuggesterAcceptanceCount, 1);
@@ -415,6 +556,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, DismissSuggestion) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoName},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(autofill::ServerFieldType::NAME_FULL, full_name_);
@@ -427,6 +573,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, SuggestWithConfirmedLength) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoPhoneNumber},
+      /*disabled_features=*/{});
+
   autofill::AutofillProfile autofill_profile(base::GenerateGUID(),
                                              autofill::test::kEmptyOrigin);
   autofill_profile.SetRawInfo(
@@ -440,6 +591,11 @@
 
 TEST_F(PersonalInfoSuggesterTest,
        DoNotAnnounceSpokenFeedbackWhenChromeVoxIsOff) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
   profile_->GetPrefs()->SetBoolean(
       ash::prefs::kAccessibilitySpokenFeedbackEnabled, false);
@@ -455,6 +611,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, AnnounceSpokenFeedbackWhenChromeVoxIsOn) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   profile_->set_profile_name(base::UTF16ToUTF8(email_));
   profile_->GetPrefs()->SetBoolean(
       ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
@@ -482,6 +643,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, DoNotShowAnnotationAfterMaxAcceptanceCount) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   for (int i = 0; i < kMaxAcceptanceCount; i++) {
     suggester_->Suggest(base::UTF8ToUTF16("my email is "));
     SendKeyboardEvent("Down");
@@ -493,6 +659,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, ShowSettingLink) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->RemoveKey(kPersonalInfoSuggesterShowSettingCount);
@@ -508,6 +679,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, DoNotShowSettingLinkAfterAcceptance) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->SetIntKey(kPersonalInfoSuggesterShowSettingCount, 0);
@@ -521,6 +697,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, ClickSettingsWithDownDownEnter) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->RemoveKey(kPersonalInfoSuggesterShowSettingCount);
@@ -537,6 +718,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, ClickSettingsWithUpEnter) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kAssistiveInputFeatureSettings);
   update->RemoveKey(kPersonalInfoSuggesterShowSettingCount);
@@ -552,6 +738,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, RecordsTimeToAccept) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   base::HistogramTester histogram_tester;
   histogram_tester.ExpectTotalCount(
       "InputMethod.Assistive.TimeToAccept.PersonalInfo", 0);
@@ -568,6 +759,11 @@
 }
 
 TEST_F(PersonalInfoSuggesterTest, RecordsTimeToDismiss) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{chromeos::features::kAssistPersonalInfoEmail},
+      /*disabled_features=*/{});
+
   base::HistogramTester histogram_tester;
   histogram_tester.ExpectTotalCount(
       "InputMethod.Assistive.TimeToAccept.PersonalInfo", 0);
diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc
index f2772aa..985eae6 100644
--- a/chrome/browser/chromeos/login/chrome_restart_request.cc
+++ b/chrome/browser/chromeos/login/chrome_restart_request.cc
@@ -86,7 +86,6 @@
     sandbox::policy::switches::kGpuSandboxAllowSysVShm,
     sandbox::policy::switches::kGpuSandboxFailuresFatal,
     sandbox::policy::switches::kNoSandbox,
-    ::switches::kBlinkSettings,
     ::switches::kDisable2dCanvasImageChromium,
     ::switches::kDisableAccelerated2dCanvas,
     ::switches::kDisableAcceleratedMjpegDecode,
@@ -103,7 +102,6 @@
     ::switches::kDisableGpuRasterization,
     ::switches::kDisableOopRasterization,
     ::switches::kDisablePepper3DImageChromium,
-    ::switches::kDisableThreadedScrolling,
     ::switches::kDisableTouchDragDrop,
     ::switches::kDisableVideoCaptureUseGpuMemoryBuffer,
     ::switches::kDisableYUVImageDecoding,
@@ -162,10 +160,12 @@
     ash::switches::kAuraLegacyPowerButton,
     ash::switches::kEnableDimShelf,
     ash::switches::kShowTaps,
+    blink::switches::kBlinkSettings,
     blink::switches::kDisableLowResTiling,
     blink::switches::kDisablePartialRaster,
     blink::switches::kDisablePreferCompositingToLCDText,
     blink::switches::kDisableRGBA4444Textures,
+    blink::switches::kDisableThreadedScrolling,
     blink::switches::kDisableZeroCopy,
     blink::switches::kEnableLowResTiling,
     blink::switches::kEnablePreferCompositingToLCDText,
diff --git a/chrome/browser/chromeos/note_taking_helper_unittest.cc b/chrome/browser/chromeos/note_taking_helper_unittest.cc
index 0aa56d7..c8faeda 100644
--- a/chrome/browser/chromeos/note_taking_helper_unittest.cc
+++ b/chrome/browser/chromeos/note_taking_helper_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/arc/fileapi/arc_file_system_bridge.h"
+#include "chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
 #include "chrome/browser/chromeos/note_taking_controller_client.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -30,6 +31,7 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
+#include "chromeos/disks/disk.h"
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
@@ -964,6 +966,21 @@
 }
 
 TEST_F(NoteTakingHelperTest, LaunchAndroidAppWithPath) {
+  chromeos::disks::DiskMountManager::InitializeForTesting(
+      new file_manager::FakeDiskMountManager);
+
+  ASSERT_TRUE(chromeos::disks::DiskMountManager::GetInstance()->AddDiskForTest(
+      chromeos::disks::Disk::Builder()
+          .SetDevicePath("/device/source_path")
+          .SetFileSystemUUID("0123-abcd")
+          .Build()));
+  ASSERT_TRUE(
+      chromeos::disks::DiskMountManager::GetInstance()->AddMountPointForTest(
+          chromeos::disks::DiskMountManager::MountPointInfo(
+              "/device/source_path", "/media/removable/UNTITLED",
+              chromeos::MOUNT_TYPE_DEVICE,
+              chromeos::disks::MOUNT_CONDITION_NONE)));
+
   const std::string kPackage = "org.chromium.package";
   std::vector<IntentHandlerInfoPtr> handlers;
   handlers.emplace_back(CreateIntentHandlerInfo("App", kPackage));
@@ -993,7 +1010,7 @@
 
   const base::FilePath kRemovablePath =
       base::FilePath(file_manager::util::kRemovableMediaPath)
-          .Append("image.jpg");
+          .Append("UNTITLED/image.jpg");
   intent_helper_.clear_handled_intents();
   file_system_->clear_handled_requests();
   helper()->LaunchAppForNewNote(profile(), kRemovablePath);
@@ -1024,6 +1041,8 @@
   histogram_tester.ExpectUniqueSample(
       NoteTakingHelper::kDefaultLaunchResultHistogramName,
       static_cast<int>(LaunchResult::ANDROID_FAILED_TO_CONVERT_PATH), 1);
+
+  chromeos::disks::DiskMountManager::Shutdown();
 }
 
 TEST_F(NoteTakingHelperTest, NoAppsAvailable) {
diff --git a/chrome/browser/diagnostics/sqlite_diagnostics.cc b/chrome/browser/diagnostics/sqlite_diagnostics.cc
index 6e6b895..60e0074 100644
--- a/chrome/browser/diagnostics/sqlite_diagnostics.cc
+++ b/chrome/browser/diagnostics/sqlite_diagnostics.cc
@@ -28,7 +28,7 @@
 #include "sql/database.h"
 #include "sql/statement.h"
 #include "storage/browser/database/database_tracker.h"
-#include "third_party/sqlite/sqlite3.h"
+#include "third_party/sqlite/sqlite3.h"  // nogncheck crbug.com/1126800
 
 #if defined(OS_CHROMEOS)
 #include "chromeos/constants/chromeos_constants.h"
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
index e7fcd26..50cf2fb 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api_chromeos.cc
@@ -557,7 +557,7 @@
   chromeos::input_method::InputMethodDescriptors descriptors;
   // Only creates descriptors for 3rd party IME extension, because the
   // descriptors for component IME extensions are managed by InputMethodUtil.
-  if (!comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) {
+  if (!comp_ext_ime_manager->IsAllowlistedExtension(extension_id)) {
     for (const auto& component : input_components) {
       DCHECK(component.type == INPUT_COMPONENT_TYPE_IME);
 
@@ -1056,7 +1056,7 @@
   chromeos::ComponentExtensionIMEManager* comp_ext_ime_manager =
       manager->GetComponentExtensionIMEManager();
 
-  if (comp_ext_ime_manager->IsWhitelistedExtension(extension->id())) {
+  if (comp_ext_ime_manager->IsAllowlistedExtension(extension->id())) {
     // Since the first party ime is not allow to uninstall, and when it's
     // unloaded unexpectedly, OS will recover the extension at once.
     // So should not unregister the IMEs. Otherwise the IME icons on the
diff --git a/chrome/browser/extensions/api/terminal/crostini_startup_status.cc b/chrome/browser/extensions/api/terminal/crostini_startup_status.cc
index 46b9d08..ccdbe0e 100644
--- a/chrome/browser/extensions/api/terminal/crostini_startup_status.cc
+++ b/chrome/browser/extensions/api/terminal/crostini_startup_status.cc
@@ -100,9 +100,6 @@
           {InstallerState::kInstallImageLoader,
            l10n_util::GetStringUTF8(
                IDS_CROSTINI_TERMINAL_STATUS_INSTALL_IMAGE_LOADER)},
-          {InstallerState::kStartConcierge,
-           l10n_util::GetStringUTF8(
-               IDS_CROSTINI_TERMINAL_STATUS_START_CONCIERGE)},
           {InstallerState::kCreateDiskImage,
            l10n_util::GetStringUTF8(
                IDS_CROSTINI_TERMINAL_STATUS_CREATE_DISK_IMAGE)},
diff --git a/chrome/browser/extensions/install_signer.cc b/chrome/browser/extensions/install_signer.cc
index ac4c075..e6f69bc 100644
--- a/chrome/browser/extensions/install_signer.cc
+++ b/chrome/browser/extensions/install_signer.cc
@@ -37,7 +37,7 @@
 #include "url/gurl.h"
 
 #if BUILDFLAG(ENABLE_RLZ)
-#include "rlz/lib/machine_id.h"
+#include "rlz/lib/machine_id.h"  // nogncheck crbug.com/1125897
 #endif
 
 namespace {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a42bf75..7b46ad9 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1890,6 +1890,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "enable-oop-print-drivers",
+    "owners": [ "awscreen", "thestig" ],
+    "expiry_milestone": 92
+  },
+  {
     "name": "enable-oop-rasterization",
     "owners": [ "enne", "khushalsagar" ],
     "expiry_milestone": 86
@@ -4344,6 +4349,11 @@
     "expiry_milestone": 88
   },
   {
+    "name": "tab-groups-auto-create",
+    "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
+    "expiry_milestone": 89
+  },
+  {
     "name": "tab-groups-collapse",
     "owners": [ "chrome-desktop-ui-seattle@google.com", "xialinyan" ],
     "expiry_milestone": 89
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 4b0f7c5a..f810a0b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2311,6 +2311,10 @@
     "Allows users to organize tabs into visually distinct groups, e.g. to "
     "separate tabs associated with different tasks.";
 
+const char kTabGroupsAutoCreateName[] = "Tab Groups Auto Create";
+const char kTabGroupsAutoCreateDescription[] =
+    "Automatically creates groups for users, if tab groups are enabled.";
+
 const char kTabGroupsCollapseName[] = "Tab Groups Collapse";
 const char kTabGroupsCollapseDescription[] =
     "Allows a tab group to be collapsible and expandable, if tab groups are "
@@ -4506,6 +4510,12 @@
     "Enables the Media Feeds background fetch feature which allows feeds to be "
     "fetched in the background. Requires #enable-media-feeds to be enabled. ";
 
+const char kEnableOopPrintDriversName[] =
+    "Enables Out-of-Process Printer Drivers";
+const char kEnableOopPrintDriversDescription[] =
+    "Enables printing interactions with the operating system to be performed "
+    "out-of-process.";
+
 const char kRemoteCopyReceiverName[] =
     "Enables the remote copy feature to receive messages";
 const char kRemoteCopyReceiverDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 94e20f0..6afb426 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1326,6 +1326,9 @@
 extern const char kTabGroupsName[];
 extern const char kTabGroupsDescription[];
 
+extern const char kTabGroupsAutoCreateName[];
+extern const char kTabGroupsAutoCreateDescription[];
+
 extern const char kTabGroupsCollapseName[];
 extern const char kTabGroupsCollapseDescription[];
 
@@ -2626,6 +2629,9 @@
 extern const char kEnableMediaFeedsBackgroundFetchName[];
 extern const char kEnableMediaFeedsBackgroundFetchDescription[];
 
+extern const char kEnableOopPrintDriversName[];
+extern const char kEnableOopPrintDriversDescription[];
+
 extern const char kRemoteCopyReceiverName[];
 extern const char kRemoteCopyReceiverDescription[];
 
diff --git a/chrome/browser/lifetime/browser_shutdown.cc b/chrome/browser/lifetime/browser_shutdown.cc
index a6025cf..0e35788 100644
--- a/chrome/browser/lifetime/browser_shutdown.cc
+++ b/chrome/browser/lifetime/browser_shutdown.cc
@@ -67,7 +67,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_RLZ)
-#include "components/rlz/rlz_tracker.h"
+#include "components/rlz/rlz_tracker.h"  // nogncheck crbug.com/1125897
 #endif
 
 #if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX)
diff --git a/chrome/browser/nearby_sharing/certificates/BUILD.gn b/chrome/browser/nearby_sharing/certificates/BUILD.gn
index 6685920..45b57b9 100644
--- a/chrome/browser/nearby_sharing/certificates/BUILD.gn
+++ b/chrome/browser/nearby_sharing/certificates/BUILD.gn
@@ -12,6 +12,7 @@
     "nearby_share_certificate_manager.h",
     "nearby_share_certificate_manager_impl.cc",
     "nearby_share_certificate_manager_impl.h",
+    "nearby_share_certificate_storage.cc",
     "nearby_share_certificate_storage.h",
     "nearby_share_certificate_storage_impl.cc",
     "nearby_share_certificate_storage_impl.h",
diff --git a/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.cc b/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.cc
index a6c19f28..c8166c3 100644
--- a/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.cc
+++ b/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.cc
@@ -87,12 +87,6 @@
 }
 
 base::Optional<base::Time>
-FakeNearbyShareCertificateStorage::NextPrivateCertificateExpirationTime()
-    const {
-  return next_private_certificate_expiration_time_;
-}
-
-base::Optional<base::Time>
 FakeNearbyShareCertificateStorage::NextPublicCertificateExpirationTime() const {
   return next_public_certificate_expiration_time_;
 }
@@ -125,10 +119,6 @@
                                                          std::move(callback));
 }
 
-void FakeNearbyShareCertificateStorage::ClearPrivateCertificates() {
-  ++num_clear_private_certificates_calls_;
-}
-
 void FakeNearbyShareCertificateStorage::ClearPublicCertificates(
     ResultCallback callback) {
   clear_public_certificates_callbacks_.push_back(std::move(callback));
@@ -139,17 +129,6 @@
   public_certificate_ids_ = ids;
 }
 
-void FakeNearbyShareCertificateStorage::SetPrivateCertificates(
-    base::Optional<std::vector<NearbySharePrivateCertificate>>
-        private_certificates) {
-  private_certificates_ = std::move(private_certificates);
-}
-
-void FakeNearbyShareCertificateStorage::SetNextPrivateCertificateExpirationTime(
-    base::Optional<base::Time> time) {
-  next_private_certificate_expiration_time_ = time;
-}
-
 void FakeNearbyShareCertificateStorage::SetNextPublicCertificateExpirationTime(
     base::Optional<base::Time> time) {
   next_public_certificate_expiration_time_ = time;
diff --git a/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.h b/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.h
index 643343f..16aec9c 100644
--- a/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.h
+++ b/chrome/browser/nearby_sharing/certificates/fake_nearby_share_certificate_storage.h
@@ -100,8 +100,6 @@
   void GetPublicCertificates(PublicCertificateCallback callback) override;
   base::Optional<std::vector<NearbySharePrivateCertificate>>
   GetPrivateCertificates() const override;
-  base::Optional<base::Time> NextPrivateCertificateExpirationTime()
-      const override;
   base::Optional<base::Time> NextPublicCertificateExpirationTime()
       const override;
   void ReplacePrivateCertificates(
@@ -117,14 +115,9 @@
       ResultCallback callback) override;
   void RemoveExpiredPublicCertificates(base::Time now,
                                        ResultCallback callback) override;
-  void ClearPrivateCertificates() override;
   void ClearPublicCertificates(ResultCallback callback) override;
 
   void SetPublicCertificateIds(const std::vector<std::string>& ids);
-  void SetPrivateCertificates(
-      base::Optional<std::vector<NearbySharePrivateCertificate>>
-          private_certificates);
-  void SetNextPrivateCertificateExpirationTime(base::Optional<base::Time> time);
   void SetNextPublicCertificateExpirationTime(base::Optional<base::Time> time);
 
   std::vector<PublicCertificateCallback>& get_public_certificates_callbacks() {
@@ -145,17 +138,11 @@
     return remove_expired_public_certificates_calls_;
   }
 
-  size_t num_clear_private_certificates_calls() {
-    return num_clear_private_certificates_calls_;
-  }
-
   std::vector<ResultCallback>& clear_public_certificates_callbacks() {
     return clear_public_certificates_callbacks_;
   }
 
  private:
-  size_t num_clear_private_certificates_calls_ = 0;
-  base::Optional<base::Time> next_private_certificate_expiration_time_;
   base::Optional<base::Time> next_public_certificate_expiration_time_;
   std::vector<std::string> public_certificate_ids_;
   base::Optional<std::vector<NearbySharePrivateCertificate>>
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.cc
index 4113bc1..ad8167f 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.cc
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.cc
@@ -56,6 +56,42 @@
   return kVisibilities.size() * kNearbyShareNumPrivateCertificates;
 }
 
+base::Optional<nearbyshare::proto::EncryptedMetadata> BuildMetadata(
+    base::Optional<std::string> device_name,
+    base::Optional<std::string> full_name,
+    base::Optional<std::string> icon_url,
+    device::BluetoothAdapter* bluetooth_adapter) {
+  nearbyshare::proto::EncryptedMetadata metadata;
+  if (!device_name) {
+    NS_LOG(WARNING)
+        << __func__
+        << ": Cannot create private certificate metadata; missing device name.";
+    return base::nullopt;
+  }
+
+  metadata.set_device_name(*device_name);
+  if (full_name) {
+    metadata.set_full_name(*full_name);
+  }
+  if (icon_url) {
+    metadata.set_icon_url(*icon_url);
+  }
+  std::array<uint8_t, 6> bytes;
+  if (bluetooth_adapter &&
+      device::ParseBluetoothAddress(bluetooth_adapter->GetAddress(), bytes)) {
+    metadata.set_bluetooth_mac_address(std::string(bytes.begin(), bytes.end()));
+  } else {
+    NS_LOG(WARNING) << __func__
+                    << ": No valid Bluetooth MAC available for private "
+                    << "certificate metadata.";
+    // TODO(https://crbug.com/1122641): Decide the best way to handle
+    // missing/invalid Bluetooth MAC addresses. Also, log a metric to track how
+    // often this happens.
+  }
+
+  return metadata;
+}
+
 void RecordGetDecryptedPublicCertificateResultMetric(
     GetDecryptedPublicCertificateResult result) {
   base::UmaHistogramEnumeration(
@@ -355,86 +391,52 @@
   NS_LOG(VERBOSE)
       << __func__
       << ": Private certificate expiration detected; refreshing certificates.";
+
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &NearbyShareCertificateManagerImpl::FinishPrivateCertificateRefresh,
+      weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NearbyShareCertificateManagerImpl::FinishPrivateCertificateRefresh(
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
   base::Time now = clock_->Now();
-  base::flat_map<nearby_share::mojom::Visibility, size_t> num_valid_certs;
-  base::flat_map<nearby_share::mojom::Visibility, base::Time> latest_not_after;
-  for (nearby_share::mojom::Visibility visibility : kVisibilities) {
-    num_valid_certs[visibility] = 0;
-    latest_not_after[visibility] = now;
-  }
+  certificate_storage_->RemoveExpiredPrivateCertificates(now);
 
-  // Remove all expired certificates.
-  std::vector<NearbySharePrivateCertificate> old_certs =
+  std::vector<NearbySharePrivateCertificate> certs =
       *certificate_storage_->GetPrivateCertificates();
-  std::vector<NearbySharePrivateCertificate> new_certs;
-  for (const NearbySharePrivateCertificate& cert : old_certs) {
-    if (IsNearbyShareCertificateExpired(
-            now, cert.not_after(),
-            /*use_public_certificate_tolerance=*/false)) {
-      continue;
-    }
-    ++num_valid_certs[cert.visibility()];
-    latest_not_after[cert.visibility()] =
-        std::max(latest_not_after[cert.visibility()], cert.not_after());
-    new_certs.push_back(cert);
-  }
-
-  if (!old_certs.empty() && new_certs.size() == old_certs.size()) {
+  if (certs.size() == NumExpectedPrivateCertificates()) {
     NS_LOG(VERBOSE) << __func__
                     << ": All private certificates are still valid.";
     private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
     return;
   }
 
-  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
-      &NearbyShareCertificateManagerImpl::FinishPrivateCertificateRefresh,
-      weak_ptr_factory_.GetWeakPtr(), std::move(new_certs),
-      std::move(num_valid_certs), std::move(latest_not_after)));
-}
+  // Determine how many private certificates of each visibility need to be
+  // created, and determine the validity period for the new certificates.
+  base::flat_map<nearby_share::mojom::Visibility, size_t> num_valid_certs;
+  base::flat_map<nearby_share::mojom::Visibility, base::Time> latest_not_after;
+  for (nearby_share::mojom::Visibility visibility : kVisibilities) {
+    num_valid_certs[visibility] = 0;
+    latest_not_after[visibility] = now;
+  }
+  for (const NearbySharePrivateCertificate& cert : certs) {
+    ++num_valid_certs[cert.visibility()];
+    latest_not_after[cert.visibility()] =
+        std::max(latest_not_after[cert.visibility()], cert.not_after());
+  }
 
-void NearbyShareCertificateManagerImpl::FinishPrivateCertificateRefresh(
-    std::vector<NearbySharePrivateCertificate> new_certs,
-    base::flat_map<nearby_share::mojom::Visibility, size_t> num_valid_certs,
-    base::flat_map<nearby_share::mojom::Visibility, base::Time>
-        latest_not_after,
-    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
-  nearbyshare::proto::EncryptedMetadata metadata;
-
-  base::Optional<std::string> device_name =
-      local_device_data_manager_->GetDeviceName();
-  if (!device_name) {
-    NS_LOG(WARNING)
-        << __func__
-        << ": Cannot create private certificates; missing device name.";
+  base::Optional<nearbyshare::proto::EncryptedMetadata> metadata =
+      BuildMetadata(local_device_data_manager_->GetDeviceName(),
+                    local_device_data_manager_->GetFullName(),
+                    local_device_data_manager_->GetIconUrl(),
+                    bluetooth_adapter.get());
+  if (!metadata) {
     private_certificate_expiration_scheduler_->HandleResult(/*success=*/false);
     return;
   }
-  metadata.set_device_name(*device_name);
 
-  base::Optional<std::string> full_name =
-      local_device_data_manager_->GetFullName();
-  base::Optional<std::string> icon_url =
-      local_device_data_manager_->GetIconUrl();
-  if (full_name) {
-    metadata.set_full_name(*full_name);
-  }
-  if (icon_url) {
-    metadata.set_icon_url(*icon_url);
-  }
-
-  std::array<uint8_t, 6> bytes;
-  if (bluetooth_adapter &&
-      device::ParseBluetoothAddress(bluetooth_adapter->GetAddress(), bytes)) {
-    metadata.set_bluetooth_mac_address(std::string(bytes.begin(), bytes.end()));
-  } else {
-    NS_LOG(WARNING) << __func__
-                    << ": No valid Bluetooth MAC available during private "
-                    << "certificate creation.";
-    // TODO(https://crbug.com/1122641): Decide the best way to handle
-    // missing/invalid Bluetooth MAC addresses. Also, log a metric to track how
-    // often this happens.
-  }
-
+  // Add new certificates if necessary. Each visibility should have
+  // kNearbyShareNumPrivateCertificates.
   NS_LOG(VERBOSE)
       << __func__ << ": Creating "
       << kNearbyShareNumPrivateCertificates -
@@ -443,18 +445,17 @@
       << kNearbyShareNumPrivateCertificates -
              num_valid_certs[nearby_share::mojom::Visibility::kSelectedContacts]
       << " selected-contacts visibility private certificates.";
-  // Add new certificates if necessary. Each visibility should have
-  // kNearbyShareNumPrivateCertificates.
   for (nearby_share::mojom::Visibility visibility : kVisibilities) {
     while (num_valid_certs[visibility] < kNearbyShareNumPrivateCertificates) {
-      new_certs.emplace_back(
-          visibility, /*not_before=*/latest_not_after[visibility], metadata);
+      certs.emplace_back(visibility,
+                         /*not_before=*/latest_not_after[visibility],
+                         *metadata);
       ++num_valid_certs[visibility];
-      latest_not_after[visibility] = new_certs.back().not_after();
+      latest_not_after[visibility] = certs.back().not_after();
     }
   }
 
-  certificate_storage_->ReplacePrivateCertificates(new_certs);
+  certificate_storage_->ReplacePrivateCertificates(certs);
   NotifyPrivateCertificatesChanged();
   private_certificate_expiration_scheduler_->HandleResult(/*success=*/true);
 
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h
index 75d86c2..e1c1733 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl.h
@@ -150,10 +150,6 @@
   void OnPrivateCertificateExpiration();
 
   void FinishPrivateCertificateRefresh(
-      std::vector<NearbySharePrivateCertificate> new_certs,
-      base::flat_map<nearby_share::mojom::Visibility, size_t> num_valid_certs,
-      base::flat_map<nearby_share::mojom::Visibility, base::Time>
-          latest_not_after,
       scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
   // Invoked by the certificate upload scheduler when private certificates need
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl_unittest.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl_unittest.cc
index 8f9037a..8f142295 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_manager_impl_unittest.cc
@@ -411,7 +411,7 @@
 };
 
 TEST_F(NearbyShareCertificateManagerImplTest, GetValidPrivateCertificate) {
-  cert_store_->SetPrivateCertificates(private_certificates_);
+  cert_store_->ReplacePrivateCertificates(private_certificates_);
   FastForward(kNearbyShareCertificateValidityPeriod * 1.5);
   auto cert = cert_manager_->GetValidPrivateCertificate(
       nearby_share::mojom::Visibility::kAllContacts);
@@ -504,7 +504,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_ValidCertificates) {
-  cert_store_->SetPrivateCertificates(private_certificates_);
+  cert_store_->ReplacePrivateCertificates(private_certificates_);
 
   cert_manager_->Start();
   HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/false,
@@ -514,7 +514,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_NoCertificates_UploadSuccess) {
-  cert_store_->SetPrivateCertificates(
+  cert_store_->ReplacePrivateCertificates(
       std::vector<NearbySharePrivateCertificate>());
 
   cert_manager_->Start();
@@ -526,7 +526,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_NoCertificates_UploadFailure) {
-  cert_store_->SetPrivateCertificates(
+  cert_store_->ReplacePrivateCertificates(
       std::vector<NearbySharePrivateCertificate>());
 
   cert_manager_->Start();
@@ -548,12 +548,12 @@
       contact_manager_->NotifyAllowlistChanged(
           were_contacts_added_to_allowlist,
           were_contacts_removed_from_allowlist);
-      if (were_contacts_removed_from_allowlist)
+      if (were_contacts_removed_from_allowlist) {
         ++num_expected_calls;
+        EXPECT_TRUE(cert_store_->GetPrivateCertificates()->empty());
+      }
 
       EXPECT_EQ(num_expected_calls,
-                cert_store_->num_clear_private_certificates_calls());
-      EXPECT_EQ(num_expected_calls,
                 private_cert_exp_scheduler_->num_immediate_requests());
     }
   }
@@ -569,12 +569,12 @@
   for (bool did_contacts_change_since_last_upload : {true, false}) {
     contact_manager_->NotifyContactsUploaded(
         did_contacts_change_since_last_upload);
-    if (did_contacts_change_since_last_upload)
+    if (did_contacts_change_since_last_upload) {
       ++num_expected_calls;
+      EXPECT_TRUE(cert_store_->GetPrivateCertificates()->empty());
+    }
 
     EXPECT_EQ(num_expected_calls,
-              cert_store_->num_clear_private_certificates_calls());
-    EXPECT_EQ(num_expected_calls,
               private_cert_exp_scheduler_->num_immediate_requests());
   }
 }
@@ -594,11 +594,10 @@
         if (did_device_name_change || did_full_name_change ||
             did_icon_url_change) {
           ++num_expected_calls;
+          EXPECT_TRUE(cert_store_->GetPrivateCertificates()->empty());
         }
 
         EXPECT_EQ(num_expected_calls,
-                  cert_store_->num_clear_private_certificates_calls());
-        EXPECT_EQ(num_expected_calls,
                   private_cert_exp_scheduler_->num_immediate_requests());
       }
     }
@@ -609,7 +608,7 @@
        RefreshPrivateCertificates_ExpiredCertificate) {
   // First certificates are expired;
   FastForward(kNearbyShareCertificateValidityPeriod * 1.5);
-  cert_store_->SetPrivateCertificates(private_certificates_);
+  cert_store_->ReplacePrivateCertificates(private_certificates_);
 
   cert_manager_->Start();
   HandlePrivateCertificateRefresh(/*expect_private_cert_refresh=*/true,
@@ -620,7 +619,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_InvalidDeviceName) {
-  cert_store_->SetPrivateCertificates(
+  cert_store_->ReplacePrivateCertificates(
       std::vector<NearbySharePrivateCertificate>());
 
   // Device name is missing in local device data manager.
@@ -635,7 +634,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_InvalidBluetoothMacAddress) {
-  cert_store_->SetPrivateCertificates(
+  cert_store_->ReplacePrivateCertificates(
       std::vector<NearbySharePrivateCertificate>());
 
   // The bluetooth adapter returns an invalid Bluetooth MAC address.
@@ -655,7 +654,7 @@
 
 TEST_F(NearbyShareCertificateManagerImplTest,
        RefreshPrivateCertificates_MissingFullNameAndIconUrl) {
-  cert_store_->SetPrivateCertificates(
+  cert_store_->ReplacePrivateCertificates(
       std::vector<NearbySharePrivateCertificate>());
 
   // Full name and icon URL are missing in local device data manager.
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.cc
new file mode 100644
index 0000000..ba93ad3
--- /dev/null
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.cc
@@ -0,0 +1,85 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "chrome/browser/nearby_sharing/certificates/common.h"
+#include "chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.h"
+
+base::Optional<base::Time>
+NearbyShareCertificateStorage::NextPrivateCertificateExpirationTime() {
+  base::Optional<std::vector<NearbySharePrivateCertificate>> certs =
+      GetPrivateCertificates();
+  if (!certs || certs->empty())
+    return base::nullopt;
+
+  base::Time min_time = base::Time::Max();
+  for (const NearbySharePrivateCertificate& cert : *certs)
+    min_time = std::min(min_time, cert.not_after());
+
+  return min_time;
+}
+
+void NearbyShareCertificateStorage::UpdatePrivateCertificate(
+    const NearbySharePrivateCertificate& private_certificate) {
+  base::Optional<std::vector<NearbySharePrivateCertificate>> certs =
+      GetPrivateCertificates();
+  if (!certs)
+    return;
+
+  auto it = std::find_if(
+      certs->begin(), certs->end(),
+      [&private_certificate](const NearbySharePrivateCertificate& cert) {
+        return cert.id() == private_certificate.id();
+      });
+  if (it == certs->end())
+    return;
+
+  *it = private_certificate;
+  ReplacePrivateCertificates(*certs);
+}
+
+void NearbyShareCertificateStorage::RemoveExpiredPrivateCertificates(
+    base::Time now) {
+  base::Optional<std::vector<NearbySharePrivateCertificate>> certs =
+      GetPrivateCertificates();
+  if (!certs)
+    return;
+
+  std::vector<NearbySharePrivateCertificate> unexpired_certs;
+  for (const NearbySharePrivateCertificate& cert : *certs) {
+    if (!IsNearbyShareCertificateExpired(
+            now, cert.not_after(),
+            /*use_public_certificate_tolerance=*/false)) {
+      unexpired_certs.push_back(cert);
+    }
+  }
+
+  ReplacePrivateCertificates(unexpired_certs);
+}
+
+void NearbyShareCertificateStorage::ClearPrivateCertificates() {
+  ReplacePrivateCertificates(std::vector<NearbySharePrivateCertificate>());
+}
+
+void NearbyShareCertificateStorage::ClearPrivateCertificatesOfVisibility(
+    nearby_share::mojom::Visibility visibility) {
+  base::Optional<std::vector<NearbySharePrivateCertificate>> certs =
+      GetPrivateCertificates();
+  if (!certs)
+    return;
+
+  bool were_certs_removed = false;
+  std::vector<NearbySharePrivateCertificate> new_certs;
+  for (const NearbySharePrivateCertificate& cert : *certs) {
+    if (cert.visibility() == visibility) {
+      were_certs_removed = true;
+    } else {
+      new_certs.push_back(cert);
+    }
+  }
+
+  if (were_certs_removed)
+    ReplacePrivateCertificates(new_certs);
+}
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.h b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.h
index 339409b..7ee94d3 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.h
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage.h
@@ -10,6 +10,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h"
 #include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
+#include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"
 
 // Stores local-device private certificates and remote-device public
 // certificates. Provides methods to help manage certificate expiration. Due to
@@ -39,8 +40,7 @@
 
   // Returns the next time a certificate expires or base::nullopt if no
   // certificates are present.
-  virtual base::Optional<base::Time> NextPrivateCertificateExpirationTime()
-      const = 0;
+  base::Optional<base::Time> NextPrivateCertificateExpirationTime();
   virtual base::Optional<base::Time> NextPublicCertificateExpirationTime()
       const = 0;
 
@@ -57,6 +57,13 @@
           public_certificates,
       ResultCallback callback) = 0;
 
+  // Overwrites an existing record with |private_certificate| if that records
+  // has the same ID . If no such record exists in storage, no action is taken.
+  // This method is necessary for updating the private certificate's list of
+  // consumed salts.
+  void UpdatePrivateCertificate(
+      const NearbySharePrivateCertificate& private_certificate);
+
   // Adds public certificates, or replaces existing certificates
   // by secret_id
   virtual void AddPublicCertificates(
@@ -64,13 +71,22 @@
           public_certificates,
       ResultCallback callback) = 0;
 
+  // Removes all private certificates from storage with expiration date after
+  // |now|.
+  void RemoveExpiredPrivateCertificates(base::Time now);
+
   // Removes all public certificates from storage with expiration date after
   // |now|.
   virtual void RemoveExpiredPublicCertificates(base::Time now,
                                                ResultCallback callback) = 0;
 
   // Delete all private certificates from memory and persistent storage.
-  virtual void ClearPrivateCertificates() = 0;
+  void ClearPrivateCertificates();
+
+  // Delete private certificates with |visibility| from memory and persistent
+  // storage.
+  void ClearPrivateCertificatesOfVisibility(
+      nearby_share::mojom::Visibility visibility);
 
   // Delete all public certificates from memory and persistent storage.
   virtual void ClearPublicCertificates(ResultCallback callback) = 0;
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
index 4c518286..eeafab4 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.cc
@@ -442,31 +442,12 @@
     if (!cert)
       return base::nullopt;
 
-    certs.emplace_back(*std::move(cert));
+    certs.push_back(*std::move(cert));
   }
   return certs;
 }
 
 base::Optional<base::Time>
-NearbyShareCertificateStorageImpl::NextPrivateCertificateExpirationTime()
-    const {
-  const base::Value* list =
-      pref_service_->Get(prefs::kNearbySharingPrivateCertificateListPrefName);
-  if (!list || list->GetList().empty())
-    return base::nullopt;
-
-  base::Time min_time = base::Time::Max();
-  for (const base::Value& cert_dict : list->GetList()) {
-    auto cert(NearbySharePrivateCertificate::FromDictionary(cert_dict));
-    if (!cert)
-      return base::nullopt;
-
-    min_time = std::min(min_time, cert->not_after());
-  }
-  return min_time;
-}
-
-base::Optional<base::Time>
 NearbyShareCertificateStorageImpl::NextPublicCertificateExpirationTime() const {
   if (public_certificate_expirations_.empty())
     return base::nullopt;
@@ -481,8 +462,8 @@
   for (const NearbySharePrivateCertificate& cert : private_certificates) {
     list.Append(cert.ToDictionary());
   }
-  NS_LOG(VERBOSE) << __func__ << ": Overwriting private certificates pref. "
-                  << private_certificates.size() << " new certificates.";
+  NS_LOG(VERBOSE) << __func__ << ": Overwriting private certificates pref with "
+                  << private_certificates.size() << " certificates.";
   pref_service_->Set(prefs::kNearbySharingPrivateCertificateListPrefName, list);
 }
 
@@ -612,10 +593,6 @@
                      std::move(callback)));
 }
 
-void NearbyShareCertificateStorageImpl::ClearPrivateCertificates() {
-  pref_service_->ClearPref(prefs::kNearbySharingPrivateCertificateListPrefName);
-}
-
 void NearbyShareCertificateStorageImpl::ClearPublicCertificates(
     ResultCallback callback) {
   if (init_status_ == InitStatus::kFailed) {
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
index d8251b65..5965194 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl.h
@@ -66,8 +66,6 @@
   void GetPublicCertificates(PublicCertificateCallback callback) override;
   base::Optional<std::vector<NearbySharePrivateCertificate>>
   GetPrivateCertificates() const override;
-  base::Optional<base::Time> NextPrivateCertificateExpirationTime()
-      const override;
   base::Optional<base::Time> NextPublicCertificateExpirationTime()
       const override;
   void ReplacePrivateCertificates(
@@ -83,7 +81,6 @@
       ResultCallback callback) override;
   void RemoveExpiredPublicCertificates(base::Time now,
                                        ResultCallback callback) override;
-  void ClearPrivateCertificates() override;
   void ClearPublicCertificates(ResultCallback callback) override;
 
  private:
@@ -131,7 +128,6 @@
       leveldb_proto::ProtoDatabase<nearbyshare::proto::PublicCertificate>>
       db_;
 
-  std::vector<NearbySharePrivateCertificate> private_certificates_;
   ExpirationList public_certificate_expirations_;
   base::queue<base::OnceClosure> deferred_callbacks_;
 };
diff --git a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl_unittest.cc b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl_unittest.cc
index 28f4ed8..83e0d8f 100644
--- a/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/certificates/nearby_share_certificate_storage_impl_unittest.cc
@@ -104,12 +104,14 @@
   return cert;
 }
 
-std::vector<NearbySharePrivateCertificate> CreatePrivateCertificates(size_t n) {
+std::vector<NearbySharePrivateCertificate> CreatePrivateCertificates(
+    size_t n,
+    nearby_share::mojom::Visibility visibility) {
   std::vector<NearbySharePrivateCertificate> certs;
   certs.reserve(n);
   for (size_t i = 0; i < n; ++i) {
-    certs.emplace_back(nearby_share::mojom::Visibility::kAllContacts,
-                       base::Time::Now(), GetNearbyShareTestMetadata());
+    certs.emplace_back(visibility, base::Time::Now(),
+                       GetNearbyShareTestMetadata());
   }
   return certs;
 }
@@ -119,6 +121,7 @@
          base::TimeDelta::FromSeconds(timestamp.seconds()) +
          base::TimeDelta::FromNanoseconds(timestamp.nanos());
 }
+
 }  // namespace
 
 class NearbyShareCertificateStorageImplTest : public ::testing::Test {
@@ -431,6 +434,33 @@
   ASSERT_EQ(0u, db_entries_.size());
 }
 
+TEST_F(NearbyShareCertificateStorageImplTest,
+       RemoveExpiredPrivateCertificates) {
+  db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
+
+  std::vector<NearbySharePrivateCertificate> certs = CreatePrivateCertificates(
+      3, nearby_share::mojom::Visibility::kAllContacts);
+  cert_store_->ReplacePrivateCertificates(certs);
+
+  std::vector<base::Time> expiration_times;
+  for (const NearbySharePrivateCertificate& cert : certs) {
+    expiration_times.push_back(cert.not_after());
+  }
+  std::sort(expiration_times.begin(), expiration_times.end());
+
+  // Set current time to exceed the expiration times of the first two
+  // certificates.
+  base::Time now = expiration_times[1];
+
+  cert_store_->RemoveExpiredPrivateCertificates(now);
+
+  certs = *cert_store_->GetPrivateCertificates();
+  ASSERT_EQ(1u, certs.size());
+  for (const NearbySharePrivateCertificate& cert : certs) {
+    EXPECT_LE(now, cert.not_after());
+  }
+}
+
 TEST_F(NearbyShareCertificateStorageImplTest, RemoveExpiredPublicCertificates) {
   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
 
@@ -464,7 +494,8 @@
 TEST_F(NearbyShareCertificateStorageImplTest, ReplaceGetPrivateCertificates) {
   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
 
-  auto certs_before = CreatePrivateCertificates(3);
+  auto certs_before = CreatePrivateCertificates(
+      3, nearby_share::mojom::Visibility::kAllContacts);
   cert_store_->ReplacePrivateCertificates(certs_before);
   auto certs_after = cert_store_->GetPrivateCertificates();
 
@@ -474,7 +505,8 @@
     EXPECT_EQ(certs_before[i].ToDictionary(), (*certs_after)[i].ToDictionary());
   }
 
-  certs_before = CreatePrivateCertificates(1);
+  certs_before = CreatePrivateCertificates(
+      1, nearby_share::mojom::Visibility::kAllContacts);
   cert_store_->ReplacePrivateCertificates(certs_before);
   certs_after = cert_store_->GetPrivateCertificates();
 
@@ -485,11 +517,37 @@
   }
 }
 
+TEST_F(NearbyShareCertificateStorageImplTest, UpdatePrivateCertificates) {
+  db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
+
+  std::vector<NearbySharePrivateCertificate> initial_certs =
+      CreatePrivateCertificates(3,
+                                nearby_share::mojom::Visibility::kAllContacts);
+  cert_store_->ReplacePrivateCertificates(initial_certs);
+
+  NearbySharePrivateCertificate cert_to_update = initial_certs[1];
+  EXPECT_EQ(initial_certs[1].ToDictionary(), cert_to_update.ToDictionary());
+  cert_to_update.EncryptMetadataKey();
+  EXPECT_NE(initial_certs[1].ToDictionary(), cert_to_update.ToDictionary());
+
+  cert_store_->UpdatePrivateCertificate(cert_to_update);
+
+  std::vector<NearbySharePrivateCertificate> new_certs =
+      *cert_store_->GetPrivateCertificates();
+  EXPECT_EQ(initial_certs.size(), new_certs.size());
+  for (size_t i = 0; i < new_certs.size(); ++i) {
+    NearbySharePrivateCertificate expected_cert =
+        i == 1 ? cert_to_update : initial_certs[i];
+    EXPECT_EQ(expected_cert.ToDictionary(), new_certs[i].ToDictionary());
+  }
+}
+
 TEST_F(NearbyShareCertificateStorageImplTest,
        NextPrivateCertificateExpirationTime) {
   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
 
-  auto certs = CreatePrivateCertificates(3);
+  auto certs = CreatePrivateCertificates(
+      3, nearby_share::mojom::Visibility::kAllContacts);
   cert_store_->ReplacePrivateCertificates(certs);
   base::Optional<base::Time> next_expiration =
       cert_store_->NextPrivateCertificateExpirationTime();
@@ -526,7 +584,8 @@
   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
 
   std::vector<NearbySharePrivateCertificate> certs_before =
-      CreatePrivateCertificates(3);
+      CreatePrivateCertificates(3,
+                                nearby_share::mojom::Visibility::kAllContacts);
   cert_store_->ReplacePrivateCertificates(certs_before);
   cert_store_->ClearPrivateCertificates();
   auto certs_after = cert_store_->GetPrivateCertificates();
@@ -534,3 +593,61 @@
   ASSERT_TRUE(certs_after.has_value());
   EXPECT_EQ(0u, certs_after->size());
 }
+
+TEST_F(NearbyShareCertificateStorageImplTest,
+       ClearPrivateCertificatesOfVisibility) {
+  db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
+
+  std::vector<NearbySharePrivateCertificate> certs_all_contacts =
+      CreatePrivateCertificates(3,
+                                nearby_share::mojom::Visibility::kAllContacts);
+  std::vector<NearbySharePrivateCertificate> certs_selected_contacts =
+      CreatePrivateCertificates(
+          3, nearby_share::mojom::Visibility::kSelectedContacts);
+  std::vector<NearbySharePrivateCertificate> all_certs;
+  all_certs.reserve(certs_all_contacts.size() + certs_selected_contacts.size());
+  all_certs.insert(all_certs.end(), certs_all_contacts.begin(),
+                   certs_all_contacts.end());
+  all_certs.insert(all_certs.end(), certs_selected_contacts.begin(),
+                   certs_selected_contacts.end());
+
+  // Remove all-contacts certs then selected-contacts certs.
+  {
+    cert_store_->ReplacePrivateCertificates(all_certs);
+    cert_store_->ClearPrivateCertificatesOfVisibility(
+        nearby_share::mojom::Visibility::kAllContacts);
+    auto certs_after = cert_store_->GetPrivateCertificates();
+    ASSERT_TRUE(certs_after.has_value());
+    ASSERT_EQ(certs_selected_contacts.size(), certs_after->size());
+    for (size_t i = 0; i < certs_selected_contacts.size(); ++i) {
+      EXPECT_EQ(certs_selected_contacts[i].ToDictionary(),
+                (*certs_after)[i].ToDictionary());
+    }
+
+    cert_store_->ClearPrivateCertificatesOfVisibility(
+        nearby_share::mojom::Visibility::kSelectedContacts);
+    certs_after = cert_store_->GetPrivateCertificates();
+    ASSERT_TRUE(certs_after.has_value());
+    EXPECT_EQ(0u, certs_after->size());
+  }
+
+  // Remove selected-contacts certs then all-contacts certs.
+  {
+    cert_store_->ReplacePrivateCertificates(all_certs);
+    cert_store_->ClearPrivateCertificatesOfVisibility(
+        nearby_share::mojom::Visibility::kSelectedContacts);
+    auto certs_after = cert_store_->GetPrivateCertificates();
+    ASSERT_TRUE(certs_after.has_value());
+    ASSERT_EQ(certs_all_contacts.size(), certs_after->size());
+    for (size_t i = 0; i < certs_all_contacts.size(); ++i) {
+      EXPECT_EQ(certs_all_contacts[i].ToDictionary(),
+                (*certs_after)[i].ToDictionary());
+    }
+
+    cert_store_->ClearPrivateCertificatesOfVisibility(
+        nearby_share::mojom::Visibility::kAllContacts);
+    certs_after = cert_store_->GetPrivateCertificates();
+    ASSERT_TRUE(certs_after.has_value());
+    EXPECT_EQ(0u, certs_after->size());
+  }
+}
diff --git a/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc b/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc
index 99dfc41..859c368 100644
--- a/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc
+++ b/chrome/browser/net/chrome_mojo_proxy_resolver_factory.cc
@@ -19,7 +19,7 @@
 #include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
 
 #if defined(OS_ANDROID)
-#include "services/proxy_resolver/proxy_resolver_factory_impl.h"
+#include "services/proxy_resolver/proxy_resolver_factory_impl.h"  // nogncheck crbug.com/1125897
 #else
 #include "content/public/browser/service_process_host.h"
 #include "services/strings/grit/services_strings.h"
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc
new file mode 100644
index 0000000..fdfb2502
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h"
+
+#include "components/translate/core/browser/translate_metrics_logger_impl.h"
+
+std::unique_ptr<TranslatePageLoadMetricsObserver>
+TranslatePageLoadMetricsObserver::CreateIfNeeded() {
+  // TODO(curranamx): Connect the new TranslateMetricsLogger to a
+  // TranslateManager. https://crbug.com/1114868.
+  std::unique_ptr<translate::TranslateMetricsLogger> translate_metrics_logger =
+      std::make_unique<translate::TranslateMetricsLoggerImpl>();
+
+  return std::make_unique<TranslatePageLoadMetricsObserver>(
+      std::move(translate_metrics_logger));
+}
+
+TranslatePageLoadMetricsObserver::TranslatePageLoadMetricsObserver(
+    std::unique_ptr<translate::TranslateMetricsLogger> translate_metrics_logger)
+    : translate_metrics_logger_(std::move(translate_metrics_logger)) {}
+
+TranslatePageLoadMetricsObserver::~TranslatePageLoadMetricsObserver() = default;
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+TranslatePageLoadMetricsObserver::OnStart(
+    content::NavigationHandle* navigation_handle,
+    const GURL& currently_committed_url,
+    bool started_in_foreground) {
+  DCHECK(translate_metrics_logger_ != nullptr);
+
+  translate_metrics_logger_->OnPageLoadStart(started_in_foreground);
+  return CONTINUE_OBSERVING;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+TranslatePageLoadMetricsObserver::OnHidden(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  DCHECK(translate_metrics_logger_ != nullptr);
+
+  translate_metrics_logger_->OnForegroundChange(false);
+  return CONTINUE_OBSERVING;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+TranslatePageLoadMetricsObserver::OnShown() {
+  DCHECK(translate_metrics_logger_ != nullptr);
+
+  translate_metrics_logger_->OnForegroundChange(true);
+  return CONTINUE_OBSERVING;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+TranslatePageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  DCHECK(translate_metrics_logger_ != nullptr);
+
+  translate_metrics_logger_->RecordMetrics(false);
+  return CONTINUE_OBSERVING;
+}
+
+void TranslatePageLoadMetricsObserver::OnComplete(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  DCHECK(translate_metrics_logger_ != nullptr);
+
+  translate_metrics_logger_->RecordMetrics(true);
+}
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h
new file mode 100644
index 0000000..8ef882e
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h
@@ -0,0 +1,55 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_TRANSLATE_PAGE_LOAD_METRICS_OBSERVER_H_
+#define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_TRANSLATE_PAGE_LOAD_METRICS_OBSERVER_H_
+
+#include <memory>
+
+#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
+
+namespace translate {
+class TranslateMetricsLogger;
+}  // namespace translate
+
+namespace content {
+class NavigationHandle;
+}  // namespace content
+
+// Observer responsible for notifying Translate of the status of a page load.
+// This information is used to log UKM and UMA metrics at a page load level, as
+// well as tracking the time a page is in the foreground and either translated
+// or not translated.
+class TranslatePageLoadMetricsObserver
+    : public page_load_metrics::PageLoadMetricsObserver {
+ public:
+  static std::unique_ptr<TranslatePageLoadMetricsObserver> CreateIfNeeded();
+
+  explicit TranslatePageLoadMetricsObserver(
+      std::unique_ptr<translate::TranslateMetricsLogger>
+          translate_metrics_logger);
+  ~TranslatePageLoadMetricsObserver() override;
+
+  TranslatePageLoadMetricsObserver(const TranslatePageLoadMetricsObserver&) =
+      delete;
+  TranslatePageLoadMetricsObserver& operator=(
+      const TranslatePageLoadMetricsObserver&) = delete;
+
+  // page_load_metrics::PageLoadMetricsObserver
+  ObservePolicy OnStart(content::NavigationHandle* navigation_handle,
+                        const GURL& currently_committed_url,
+                        bool started_in_foreground) override;
+  ObservePolicy OnHidden(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  ObservePolicy OnShown() override;
+  ObservePolicy FlushMetricsOnAppEnterBackground(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnComplete(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+
+ private:
+  std::unique_ptr<translate::TranslateMetricsLogger> translate_metrics_logger_;
+};
+
+#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_TRANSLATE_PAGE_LOAD_METRICS_OBSERVER_H_
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
new file mode 100644
index 0000000..3d31eb1
--- /dev/null
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h"
+
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
+#include "components/page_load_metrics/browser/page_load_tracker.h"
+#include "components/translate/core/browser/translate_metrics_logger.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class MockTranslateMetricsLogger : public translate::TranslateMetricsLogger {
+ public:
+  MOCK_METHOD1(OnPageLoadStart, void(bool));
+  MOCK_METHOD1(OnForegroundChange, void(bool));
+  MOCK_METHOD1(RecordMetrics, void(bool));
+};
+
+// Wraps the above MockTranslateMetricsLogger so that test can retain a pointer
+// to the MockTranslateMetricsLogger after the TranslatePageLoadMetricsObserver
+// is done with it.
+class MockTranslateMetricsLoggerContainer
+    : public translate::TranslateMetricsLogger {
+ public:
+  explicit MockTranslateMetricsLoggerContainer(
+      MockTranslateMetricsLogger* mock_translate_metrics_logger)
+      : mock_translate_metrics_logger_(mock_translate_metrics_logger) {}
+
+  void OnPageLoadStart(bool is_foreground) override {
+    mock_translate_metrics_logger_->OnPageLoadStart(is_foreground);
+  }
+
+  void OnForegroundChange(bool is_foreground) override {
+    mock_translate_metrics_logger_->OnForegroundChange(is_foreground);
+  }
+
+  void RecordMetrics(bool is_final) override {
+    mock_translate_metrics_logger_->RecordMetrics(is_final);
+  }
+
+ private:
+  MockTranslateMetricsLogger* mock_translate_metrics_logger_;  // Weak.
+};
+
+class TranslatePageLoadMetricsObserverTest
+    : public page_load_metrics::PageLoadMetricsObserverTestHarness {
+ public:
+  void SetUp() override {
+    PageLoadMetricsObserverTestHarness::SetUp();
+
+    // Creates the MockTranslateMetricsLogger that will be used for this test.
+    mock_translate_metrics_logger_ =
+        std::make_unique<MockTranslateMetricsLogger>();
+  }
+
+  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
+    MockTranslateMetricsLogger* raw_mock_translate_metrics_logger =
+        mock_translate_metrics_logger_.get();
+
+    // Wraps the raw pointer in a container.
+    std::unique_ptr<MockTranslateMetricsLoggerContainer>
+        mock_translate_metrics_logger_container =
+            std::make_unique<MockTranslateMetricsLoggerContainer>(
+                raw_mock_translate_metrics_logger);
+
+    tracker->AddObserver(std::make_unique<TranslatePageLoadMetricsObserver>(
+        std::move(mock_translate_metrics_logger_container)));
+  }
+
+  MockTranslateMetricsLogger& mock_translate_metrics_logger() const {
+    return *mock_translate_metrics_logger_;
+  }
+
+ private:
+  // This is the TranslateMetricsLoggers used in a test.It is owned by the
+  // TranslatePageLoadMetricsObserverTest.
+  std::unique_ptr<MockTranslateMetricsLogger> mock_translate_metrics_logger_;
+};
+
+TEST_F(TranslatePageLoadMetricsObserverTest, SinglePageLoad) {
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(false)).Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnForegroundChange(testing::_))
+      .Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(false)).Times(0);
+
+  NavigateAndCommit(GURL("https://www.example.com"));
+  tester()->NavigateToUntrackedUrl();
+}
+
+TEST_F(TranslatePageLoadMetricsObserverTest, AppEntersBackground) {
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(false)).Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnForegroundChange(testing::_))
+      .Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(false)).Times(1);
+
+  NavigateAndCommit(GURL("https://www.example.com"));
+  tester()->SimulateAppEnterBackground();
+  tester()->NavigateToUntrackedUrl();
+}
+
+TEST_F(TranslatePageLoadMetricsObserverTest, RepeatedAppEntersBackground) {
+  int num_times_enter_background = 100;
+
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnPageLoadStart(false)).Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), OnForegroundChange(testing::_))
+      .Times(0);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(true)).Times(1);
+  EXPECT_CALL(mock_translate_metrics_logger(), RecordMetrics(false))
+      .Times(num_times_enter_background);
+
+  NavigateAndCommit(GURL("https://www.example.com"));
+  for (int i = 0; i < num_times_enter_background; ++i)
+    tester()->SimulateAppEnterBackground();
+
+  tester()->NavigateToUntrackedUrl();
+}
+
+// TODO(curranmax): Add unit tests that confirm behavior when the hidden/shown.
+// status of the tab changes. https://crbug.com/1114868.
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
index f20c73f..3763314 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/page_load_metrics/observers/signed_exchange_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/tab_restore_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/third_party_metrics_observer.h"
+#include "chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h"
 #include "chrome/browser/prerender/chrome_prerender_contents_delegate.h"
 #include "chrome/browser/profiles/profile.h"
@@ -165,6 +166,10 @@
       SecurityStatePageLoadMetricsObserver::MaybeCreateForProfile(
           web_contents()->GetBrowserContext()));
   tracker->AddObserver(std::make_unique<DataUseMetricsObserver>());
+  std::unique_ptr<TranslatePageLoadMetricsObserver> translate_observer =
+      TranslatePageLoadMetricsObserver::CreateIfNeeded();
+  if (translate_observer != nullptr)
+    tracker->AddObserver(std::move(translate_observer));
 }
 
 bool PageLoadMetricsEmbedder::IsPrerendering() const {
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 953e8e5..a672abf 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -126,7 +126,7 @@
 #include "chromeos/dbus/power/power_policy_controller.h"
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
 #include "components/arc/arc_prefs.h"
-#include "components/drive/drive_pref_names.h"
+#include "components/drive/drive_pref_names.h"  // nogncheck crbug.com/1125897
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #else  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/policy/messaging_layer/encryption/encryption_module.cc b/chrome/browser/policy/messaging_layer/encryption/encryption_module.cc
index 4be5d74..7e6c26d 100644
--- a/chrome/browser/policy/messaging_layer/encryption/encryption_module.cc
+++ b/chrome/browser/policy/messaging_layer/encryption/encryption_module.cc
@@ -4,10 +4,11 @@
 
 #include "chrome/browser/policy/messaging_layer/encryption/encryption_module.h"
 
+#include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
 #include "base/strings/string_piece.h"
-#include "base/test/task_environment.h"
+#include "base/task/thread_pool.h"
 #include "chrome/browser/policy/messaging_layer/util/status.h"
 #include "chrome/browser/policy/messaging_layer/util/statusor.h"
 #include "components/policy/proto/record.pb.h"
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index c5734a3..e2f18aa 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -222,7 +222,7 @@
 #include "chrome/browser/first_run/android/first_run_prefs.h"
 #include "chrome/browser/media/android/cdm/media_drm_origin_id_manager.h"
 #include "chrome/browser/ssl/known_interception_disclosure_infobar_delegate.h"
-#include "components/cdm/browser/media_drm_storage_impl.h"
+#include "components/cdm/browser/media_drm_storage_impl.h"  // nogncheck crbug.com/1125897
 #include "components/feed/buildflags.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
 #include "components/games/core/games_prefs.h"
diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc
index 105e7ff..04f82a8 100644
--- a/chrome/browser/prefs/chrome_pref_service_factory.cc
+++ b/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -77,7 +77,7 @@
 #if defined(OS_WIN)
 #include "base/enterprise_util.h"
 #if BUILDFLAG(ENABLE_RLZ)
-#include "rlz/lib/machine_id.h"
+#include "rlz/lib/machine_id.h"  // nogncheck crbug.com/1125897
 #endif  // BUILDFLAG(ENABLE_RLZ)
 #endif  // defined(OS_WIN)
 
diff --git a/chrome/browser/printing/pdf_to_emf_converter.cc b/chrome/browser/printing/pdf_to_emf_converter.cc
index c9a0a51..09c5377 100644
--- a/chrome/browser/printing/pdf_to_emf_converter.cc
+++ b/chrome/browser/printing/pdf_to_emf_converter.cc
@@ -132,7 +132,8 @@
  private:
   class GetPageCallbackData {
    public:
-    GetPageCallbackData(int page_number, PdfConverter::GetPageCallback callback)
+    GetPageCallbackData(uint32_t page_number,
+                        PdfConverter::GetPageCallback callback)
         : page_number_(page_number), callback_(callback) {}
 
     GetPageCallbackData(GetPageCallbackData&& other) {
@@ -145,12 +146,12 @@
       return *this;
     }
 
-    int page_number() const { return page_number_; }
+    uint32_t page_number() const { return page_number_; }
 
     PdfConverter::GetPageCallback callback() const { return callback_; }
 
    private:
-    int page_number_;
+    uint32_t page_number_;
 
     PdfConverter::GetPageCallback callback_;
 
@@ -159,7 +160,7 @@
 
   void Initialize(scoped_refptr<base::RefCountedMemory> data);
 
-  void GetPage(int page_number,
+  void GetPage(uint32_t page_number,
                PdfConverter::GetPageCallback get_page_callback) override;
 
   void Stop();
@@ -305,7 +306,7 @@
 }
 
 void PdfConverterImpl::GetPage(
-    int page_number,
+    uint32_t page_number,
     PdfConverter::GetPageCallback get_page_callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(pdf_to_emf_converter_.is_bound());
diff --git a/chrome/browser/printing/pdf_to_emf_converter.h b/chrome/browser/printing/pdf_to_emf_converter.h
index f1d6791..0f0f2e8 100644
--- a/chrome/browser/printing/pdf_to_emf_converter.h
+++ b/chrome/browser/printing/pdf_to_emf_converter.h
@@ -17,9 +17,9 @@
 
 class PdfConverter {
  public:
-  using StartCallback = base::OnceCallback<void(int page_count)>;
+  using StartCallback = base::OnceCallback<void(uint32_t page_count)>;
   using GetPageCallback =
-      base::RepeatingCallback<void(int page_number,
+      base::RepeatingCallback<void(uint32_t page_number,
                                    float scale_factor,
                                    std::unique_ptr<MetafilePlayer> file)>;
   virtual ~PdfConverter();
@@ -35,7 +35,8 @@
   // PDF provided in Start() call.
   // Calls |get_page_callback| after conversion. |emf| of callback in not NULL
   // if conversion succeeded.
-  virtual void GetPage(int page_number, GetPageCallback get_page_callback) = 0;
+  virtual void GetPage(uint32_t page_number,
+                       GetPageCallback get_page_callback) = 0;
 };
 
 // Object used by tests to exercise the temporary file creation failure code
diff --git a/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc b/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc
index 46d8115..dd2837e 100644
--- a/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc
+++ b/chrome/browser/printing/pdf_to_emf_converter_browsertest.cc
@@ -4,8 +4,11 @@
 
 #include "chrome/browser/printing/pdf_to_emf_converter.h"
 
+#include <stdint.h>
 #include <windows.h>
 
+#include <limits>
+
 #include "base/bind.h"
 #include "base/containers/span.h"
 #include "base/files/file_util.h"
@@ -32,17 +35,19 @@
 
 constexpr size_t kHeaderSize = sizeof(ENHMETAHEADER);
 
+constexpr uint32_t kInvalidPageCount = std::numeric_limits<uint32_t>::max();
+
 void StartCallbackImpl(base::OnceClosure quit_closure,
-                       int* page_count_out,
-                       int page_count_in) {
+                       uint32_t* page_count_out,
+                       uint32_t page_count_in) {
   *page_count_out = page_count_in;
   std::move(quit_closure).Run();
 }
 
 void GetPageCallbackImpl(base::OnceClosure quit_closure,
-                         int* page_number_out,
+                         uint32_t* page_number_out,
                          std::unique_ptr<MetafilePlayer>* file_out,
-                         int page_number_in,
+                         uint32_t page_number_in,
                          float scale_factor,
                          std::unique_ptr<MetafilePlayer> file_in) {
   *page_number_out = page_number_in;
@@ -133,9 +138,9 @@
   }
 
   bool StartPdfConverter(const PdfRenderSettings& pdf_settings,
-                         int expected_page_count) {
+                         uint32_t expected_page_count) {
     base::RunLoop run_loop;
-    int page_count = -1;
+    uint32_t page_count = kInvalidPageCount;
     pdf_converter_ = PdfConverter::StartPdfConverter(
         test_input_, pdf_settings,
         base::BindOnce(&StartCallbackImpl, run_loop.QuitClosure(),
@@ -144,9 +149,9 @@
     return pdf_converter_ && (expected_page_count == page_count);
   }
 
-  bool GetPage(int page_number_in) {
+  bool GetPage(uint32_t page_number_in) {
     base::RunLoop run_loop;
-    int page_number = -1;
+    uint32_t page_number = kInvalidPageCount;
     pdf_converter_->GetPage(
         page_number_in,
         base::BindRepeating(&GetPageCallbackImpl, run_loop.QuitClosure(),
@@ -227,12 +232,12 @@
   ScopedSimulateFailureCreatingTempFileForTests fail_creating_temp_file;
 
   base::RunLoop run_loop;
-  int page_count = -1;
+  uint32_t page_count = kInvalidPageCount;
   std::unique_ptr<PdfConverter> pdf_converter = PdfConverter::StartPdfConverter(
       base::MakeRefCounted<base::RefCountedStaticMemory>(), PdfRenderSettings(),
       base::BindOnce(&StartCallbackImpl, run_loop.QuitClosure(), &page_count));
   run_loop.Run();
-  EXPECT_EQ(0, page_count);
+  EXPECT_EQ(0u, page_count);
 }
 
 IN_PROC_BROWSER_TEST_F(PdfToEmfConverterBrowserTest, FailureBadPdf) {
@@ -240,12 +245,12 @@
       base::MakeRefCounted<base::RefCountedStaticMemory>("0123456789", 10);
 
   base::RunLoop run_loop;
-  int page_count = -1;
+  uint32_t page_count = kInvalidPageCount;
   std::unique_ptr<PdfConverter> pdf_converter = PdfConverter::StartPdfConverter(
       bad_pdf_data, PdfRenderSettings(),
       base::BindOnce(&StartCallbackImpl, run_loop.QuitClosure(), &page_count));
   run_loop.Run();
-  EXPECT_EQ(0, page_count);
+  EXPECT_EQ(0u, page_count);
 }
 
 IN_PROC_BROWSER_TEST_F(PdfToEmfConverterBrowserTest, EmfBasic) {
@@ -253,11 +258,11 @@
       kLetter200DpiRect, gfx::Point(0, 0), k200DpiSize,
       /*autorotate=*/false,
       /*use_color=*/true, PdfRenderSettings::Mode::NORMAL);
-  constexpr int kNumberOfPages = 3;
+  constexpr uint32_t kNumberOfPages = 3;
 
   ASSERT_TRUE(GetTestInput("pdf_converter_basic.pdf"));
   ASSERT_TRUE(StartPdfConverter(pdf_settings, kNumberOfPages));
-  for (int i = 0; i < kNumberOfPages; ++i) {
+  for (uint32_t i = 0; i < kNumberOfPages; ++i) {
     ASSERT_TRUE(GetPage(i));
     ASSERT_TRUE(GetPageExpectedEmfData(
         GetFileNameForPageNumber("pdf_converter_basic_emf_page_", i)));
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index aacd94c..32fc836 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -84,7 +84,7 @@
 
  private:
   // PrintPreviewUI::TestDelegate:
-  void DidGetPreviewPageCount(int page_count) override {
+  void DidGetPreviewPageCount(uint32_t page_count) override {
     total_page_count_ = page_count;
   }
 
@@ -109,8 +109,8 @@
   }
 
   base::Optional<content::DOMMessageQueue> queue_;
-  int total_page_count_ = 1;
-  int rendered_page_count_ = 0;
+  uint32_t total_page_count_ = 1;
+  uint32_t rendered_page_count_ = 0;
   content::WebContents* preview_dialog_ = nullptr;
   base::RunLoop* run_loop_ = nullptr;
 
diff --git a/chrome/browser/printing/print_job.cc b/chrome/browser/printing/print_job.cc
index 792f6ac..4f3b681 100644
--- a/chrome/browser/printing/print_job.cc
+++ b/chrome/browser/printing/print_job.cc
@@ -85,7 +85,7 @@
 
 void PrintJob::Initialize(std::unique_ptr<PrinterQuery> query,
                           const base::string16& name,
-                          int page_count) {
+                          uint32_t page_count) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(!worker_);
   DCHECK(!is_job_pending_);
@@ -98,7 +98,7 @@
 #if defined(OS_WIN)
   pdf_page_mapping_ = PageRange::GetPages(settings->ranges());
   if (pdf_page_mapping_.empty()) {
-    for (int i = 0; i < page_count; i++)
+    for (uint32_t i = 0; i < page_count; i++)
       pdf_page_mapping_.push_back(i);
   }
 #endif
@@ -115,12 +115,13 @@
 
 #if defined(OS_WIN)
 // static
-std::vector<int> PrintJob::GetFullPageMapping(const std::vector<int>& pages,
-                                              int total_page_count) {
-  std::vector<int> mapping(total_page_count, -1);
-  for (int page_number : pages) {
+std::vector<uint32_t> PrintJob::GetFullPageMapping(
+    const std::vector<uint32_t>& pages,
+    uint32_t total_page_count) {
+  std::vector<uint32_t> mapping(total_page_count, kInvalidPageIndex);
+  for (uint32_t page_number : pages) {
     // Make sure the page is in range.
-    if (page_number >= 0 && page_number < total_page_count)
+    if (page_number < total_page_count)
       mapping[page_number] = page_number;
   }
   return mapping;
@@ -319,13 +320,13 @@
       converter_.reset();
   }
 
-  void set_page_count(int page_count) { page_count_ = page_count; }
+  void set_page_count(uint32_t page_count) { page_count_ = page_count; }
   const gfx::Size& page_size() const { return page_size_; }
   const gfx::Rect& content_area() const { return content_area_; }
 
  private:
-  int page_count_;
-  int current_page_;
+  uint32_t page_count_;
+  uint32_t current_page_;
   int pages_in_progress_;
   gfx::Size page_size_;
   gfx::Rect content_area_;
@@ -379,7 +380,7 @@
       base::BindOnce(&PrintJob::OnPdfConversionStarted, this));
 }
 
-void PrintJob::OnPdfConversionStarted(int page_count) {
+void PrintJob::OnPdfConversionStarted(uint32_t page_count) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (page_count <= 0) {
@@ -394,13 +395,13 @@
       base::BindRepeating(&PrintJob::OnPdfPageConverted, this));
 }
 
-void PrintJob::OnPdfPageConverted(int page_number,
+void PrintJob::OnPdfPageConverted(uint32_t page_number,
                                   float scale_factor,
                                   std::unique_ptr<MetafilePlayer> metafile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(pdf_conversion_state_);
-  if (!document_ || !metafile || page_number < 0 ||
-      static_cast<size_t>(page_number) >= pdf_page_mapping_.size()) {
+  if (!document_ || !metafile || page_number == kInvalidPageIndex ||
+      page_number >= pdf_page_mapping_.size()) {
     // Be sure to live long enough.
     scoped_refptr<PrintJob> handle(this);
     pdf_conversion_state_.reset();
@@ -410,7 +411,7 @@
 
   // Add the page to the document if it is one of the pages requested by the
   // user. If it is not, ignore it.
-  if (pdf_page_mapping_[page_number] != -1) {
+  if (pdf_page_mapping_[page_number] != kInvalidPageIndex) {
     // Update the rendered document. It will send notifications to the listener.
     document_->SetPage(pdf_page_mapping_[page_number], std::move(metafile),
                        scale_factor, pdf_conversion_state_->page_size(),
diff --git a/chrome/browser/printing/print_job.h b/chrome/browser/printing/print_job.h
index 0eadb45..beb577a 100644
--- a/chrome/browser/printing/print_job.h
+++ b/chrome/browser/printing/print_job.h
@@ -67,7 +67,7 @@
   // the settings.
   virtual void Initialize(std::unique_ptr<PrinterQuery> query,
                           const base::string16& name,
-                          int page_count);
+                          uint32_t page_count);
 
 #if defined(OS_WIN)
   void StartConversionToNativeFormat(
@@ -188,14 +188,15 @@
       scoped_refptr<base::RefCountedMemory> bytes,
       const gfx::Size& page_size);
 
-  void OnPdfConversionStarted(int page_count);
-  void OnPdfPageConverted(int page_number,
+  void OnPdfConversionStarted(uint32_t page_count);
+  void OnPdfPageConverted(uint32_t page_number,
                           float scale_factor,
                           std::unique_ptr<MetafilePlayer> metafile);
 
   // Helper method to do the work for ResetPageMapping(). Split for unit tests.
-  static std::vector<int> GetFullPageMapping(const std::vector<int>& pages,
-                                             int total_page_count);
+  static std::vector<uint32_t> GetFullPageMapping(
+      const std::vector<uint32_t>& pages,
+      uint32_t total_page_count);
 #endif  // defined(OS_WIN)
 
   content::NotificationRegistrar registrar_;
@@ -218,7 +219,7 @@
 #if defined(OS_WIN)
   class PdfConversionState;
   std::unique_ptr<PdfConversionState> pdf_conversion_state_;
-  std::vector<int> pdf_page_mapping_;
+  std::vector<uint32_t> pdf_page_mapping_;
 #endif  // defined(OS_WIN)
 
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/printing/print_job_unittest.cc b/chrome/browser/printing/print_job_unittest.cc
index 56414e8..1afc7e0 100644
--- a/chrome/browser/printing/print_job_unittest.cc
+++ b/chrome/browser/printing/print_job_unittest.cc
@@ -149,28 +149,33 @@
   content::BrowserTaskEnvironment task_environment;
 
   int page_count = 4;
-  std::vector<int> input_full = {0, 1, 2, 3};
-  std::vector<int> expected_output_full = {0, 1, 2, 3};
+  std::vector<uint32_t> input_full = {0, 1, 2, 3};
+  std::vector<uint32_t> expected_output_full = {0, 1, 2, 3};
   EXPECT_EQ(expected_output_full,
             PrintJob::GetFullPageMapping(input_full, page_count));
 
-  std::vector<int> input_12 = {1, 2};
-  std::vector<int> expected_output_12 = {-1, 1, 2, -1};
+  std::vector<uint32_t> input_12 = {1, 2};
+  std::vector<uint32_t> expected_output_12 = {kInvalidPageIndex, 1, 2,
+                                              kInvalidPageIndex};
   EXPECT_EQ(expected_output_12,
             PrintJob::GetFullPageMapping(input_12, page_count));
 
-  std::vector<int> input_03 = {0, 3};
-  std::vector<int> expected_output_03 = {0, -1, -1, 3};
+  std::vector<uint32_t> input_03 = {0, 3};
+  std::vector<uint32_t> expected_output_03 = {0, kInvalidPageIndex,
+                                              kInvalidPageIndex, 3};
   EXPECT_EQ(expected_output_03,
             PrintJob::GetFullPageMapping(input_03, page_count));
 
-  std::vector<int> input_0 = {0};
-  std::vector<int> expected_output_0 = {0, -1, -1, -1};
+  std::vector<uint32_t> input_0 = {0};
+  std::vector<uint32_t> expected_output_0 = {
+      0, kInvalidPageIndex, kInvalidPageIndex, kInvalidPageIndex};
   EXPECT_EQ(expected_output_0,
             PrintJob::GetFullPageMapping(input_0, page_count));
 
-  std::vector<int> input_invalid = {4, 100};
-  std::vector<int> expected_output_invalid = {-1, -1, -1, -1};
+  std::vector<uint32_t> input_invalid = {4, 100};
+  std::vector<uint32_t> expected_output_invalid = {
+      kInvalidPageIndex, kInvalidPageIndex, kInvalidPageIndex,
+      kInvalidPageIndex};
   EXPECT_EQ(expected_output_invalid,
             PrintJob::GetFullPageMapping(input_invalid, page_count));
 }
diff --git a/chrome/browser/printing/print_job_worker.cc b/chrome/browser/printing/print_job_worker.cc
index 17788cb2..49894fb 100644
--- a/chrome/browser/printing/print_job_worker.cc
+++ b/chrome/browser/printing/print_job_worker.cc
@@ -13,6 +13,7 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/location.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
@@ -153,7 +154,7 @@
 }
 
 void PrintJobWorker::GetSettings(bool ask_user_for_settings,
-                                 int document_page_count,
+                                 uint32_t document_page_count,
                                  bool has_selection,
                                  mojom::MarginType margin_type,
                                  bool is_scripted,
@@ -263,11 +264,12 @@
   std::move(callback).Run(printing_context_->TakeAndResetSettings(), result);
 }
 
-void PrintJobWorker::GetSettingsWithUI(int document_page_count,
+void PrintJobWorker::GetSettingsWithUI(uint32_t document_page_count,
                                        bool has_selection,
                                        bool is_scripted,
                                        SettingsCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_LE(document_page_count, kMaxPageCount);
 
   content::WebContents* web_contents = GetWebContents();
 
@@ -297,7 +299,7 @@
     web_contents->ExitFullscreen(true);
 
   printing_context_->AskUserForSettings(
-      document_page_count, has_selection, is_scripted,
+      base::checked_cast<int>(document_page_count), has_selection, is_scripted,
       base::BindOnce(&PrintJobWorker::GetSettingsDone,
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -417,7 +419,7 @@
   }
 
   while (true) {
-    scoped_refptr<PrintedPage> page = document_->GetPage(page_number_.ToInt());
+    scoped_refptr<PrintedPage> page = document_->GetPage(page_number_.ToUint());
     if (!page) {
       PostWaitForPage();
       return false;
diff --git a/chrome/browser/printing/print_job_worker.h b/chrome/browser/printing/print_job_worker.h
index 429c2b7..2494da2 100644
--- a/chrome/browser/printing/print_job_worker.h
+++ b/chrome/browser/printing/print_job_worker.h
@@ -51,7 +51,7 @@
   // |is_scripted| should be true for calls coming straight from window.print().
   // |is_modifiable| implies HTML and not other formats like PDF.
   void GetSettings(bool ask_user_for_settings,
-                   int document_page_count,
+                   uint32_t document_page_count,
                    bool has_selection,
                    mojom::MarginType margin_type,
                    bool is_scripted,
@@ -142,7 +142,7 @@
   // Asks the user for print settings. Must be called on the UI thread.
   // Required on Mac and Linux. Windows can display UI from non-main threads,
   // but sticks with this for consistency.
-  void GetSettingsWithUI(int document_page_count,
+  void GetSettingsWithUI(uint32_t document_page_count,
                          bool has_selection,
                          bool is_scripted,
                          SettingsCallback callback);
diff --git a/chrome/browser/printing/print_preview_message_handler.cc b/chrome/browser/printing/print_preview_message_handler.cc
index e51a057..8d0277b 100644
--- a/chrome/browser/printing/print_preview_message_handler.cc
+++ b/chrome/browser/printing/print_preview_message_handler.cc
@@ -14,6 +14,7 @@
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/numerics/safe_conversions.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/printing/pdf_nup_converter_client.h"
 #include "chrome/browser/printing/print_job_manager.h"
@@ -65,8 +66,8 @@
   return IsOopifEnabled() && print_preview_ui->source_is_modifiable();
 }
 
-bool IsValidPageNumber(int page_number, int page_count) {
-  return page_number >= 0 && page_number < page_count;
+bool IsValidPageNumber(uint32_t page_number, uint32_t page_count) {
+  return page_number < page_count;
 }
 
 }  // namespace
@@ -112,12 +113,13 @@
 void PrintPreviewMessageHandler::OnDidStartPreview(
     const mojom::DidStartPreviewParams& params,
     const mojom::PreviewIds& ids) {
-  if (params.page_count <= 0 || params.pages_to_render.empty()) {
+  if (params.page_count == 0 || params.page_count > kMaxPageCount ||
+      params.pages_to_render.empty()) {
     NOTREACHED();
     return;
   }
 
-  for (int page_number : params.pages_to_render) {
+  for (uint32_t page_number : params.pages_to_render) {
     if (!IsValidPageNumber(page_number, params.page_count)) {
       NOTREACHED();
       return;
@@ -176,10 +178,12 @@
     content::RenderFrameHost* render_frame_host,
     const mojom::DidPreviewPageParams& params,
     const mojom::PreviewIds& ids) {
-  int page_number = params.page_number;
+  uint32_t page_number = params.page_number;
   const mojom::DidPrintContentParams& content = *params.content;
-  if (page_number < FIRST_PAGE_INDEX || !content.metafile_data_region.IsValid())
+  if (page_number == kInvalidPageIndex ||
+      !content.metafile_data_region.IsValid()) {
     return;
+  }
 
   PrintPreviewUI* print_preview_ui = GetPrintPreviewUI(ids.ui_id);
   if (!print_preview_ui)
@@ -285,7 +289,7 @@
 
 void PrintPreviewMessageHandler::NotifyUIPreviewPageReady(
     PrintPreviewUI* print_preview_ui,
-    int page_number,
+    uint32_t page_number,
     const mojom::PreviewIds& ids,
     scoped_refptr<base::RefCountedMemory> data_bytes) {
   if (!data_bytes || !data_bytes->size())
@@ -315,7 +319,7 @@
 }
 
 void PrintPreviewMessageHandler::OnCompositePdfPageDone(
-    int page_number,
+    uint32_t page_number,
     int document_cookie,
     const mojom::PreviewIds& ids,
     mojom::PrintCompositor::Status status,
@@ -343,15 +347,17 @@
         base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region));
   } else {
     print_preview_ui->AddPdfPageForNupConversion(std::move(region));
-    int current_page_index =
+    uint32_t current_page_index =
         print_preview_ui->GetPageToNupConvertIndex(page_number);
-    if (current_page_index == -1) {
+    if (current_page_index == kInvalidPageIndex) {
       return;
     }
 
     if (((current_page_index + 1) % pages_per_sheet) == 0 ||
         print_preview_ui->LastPageComposited(page_number)) {
-      int new_page_number = current_page_index / pages_per_sheet;
+      uint32_t new_page_number =
+          base::checked_cast<uint32_t>(current_page_index / pages_per_sheet);
+      DCHECK_NE(new_page_number, kInvalidPageIndex);
       std::vector<base::ReadOnlySharedMemoryRegion> pdf_page_regions =
           print_preview_ui->TakePagesForNupConvert();
 
@@ -376,7 +382,7 @@
 }
 
 void PrintPreviewMessageHandler::OnNupPdfConvertDone(
-    int page_number,
+    uint32_t page_number,
     const mojom::PreviewIds& ids,
     mojom::PdfNupConverter::Status status,
     base::ReadOnlySharedMemoryRegion region) {
diff --git a/chrome/browser/printing/print_preview_message_handler.h b/chrome/browser/printing/print_preview_message_handler.h
index f0ef120..afcb879 100644
--- a/chrome/browser/printing/print_preview_message_handler.h
+++ b/chrome/browser/printing/print_preview_message_handler.h
@@ -81,7 +81,7 @@
 
   void NotifyUIPreviewPageReady(
       PrintPreviewUI* print_preview_ui,
-      int page_number,
+      uint32_t page_number,
       const mojom::PreviewIds& ids,
       scoped_refptr<base::RefCountedMemory> data_bytes);
   void NotifyUIPreviewDocumentReady(
@@ -90,7 +90,7 @@
       scoped_refptr<base::RefCountedMemory> data_bytes);
 
   // Callbacks for print compositor client.
-  void OnCompositePdfPageDone(int page_number,
+  void OnCompositePdfPageDone(uint32_t page_number,
                               int document_cookie,
                               const mojom::PreviewIds& ids,
                               mojom::PrintCompositor::Status status,
@@ -102,7 +102,7 @@
   void OnPrepareForDocumentToPdfDone(const mojom::PreviewIds& ids,
                                      mojom::PrintCompositor::Status status);
 
-  void OnNupPdfConvertDone(int page_number,
+  void OnNupPdfConvertDone(uint32_t page_number,
                            const mojom::PreviewIds& ids,
                            mojom::PdfNupConverter::Status status,
                            base::ReadOnlySharedMemoryRegion region);
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 915c981..fb2a4c0 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -13,6 +13,7 @@
 #include "base/location.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
@@ -187,7 +188,7 @@
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
 void PrintViewManagerBase::OnPrintSettingsDone(
     scoped_refptr<base::RefCountedMemory> print_data,
-    int page_count,
+    uint32_t page_count,
     PrinterHandler::PrintCallback callback,
     std::unique_ptr<printing::PrinterQuery> printer_query) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -226,7 +227,7 @@
 
 void PrintViewManagerBase::StartLocalPrintJob(
     scoped_refptr<base::RefCountedMemory> print_data,
-    int page_count,
+    uint32_t page_count,
     int cookie,
     PrinterHandler::PrintCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -274,7 +275,7 @@
 }
 
 void PrintViewManagerBase::DidGetPrintedPagesCount(int32_t cookie,
-                                                   int32_t number_pages) {
+                                                   uint32_t number_pages) {
   PrintManager::DidGetPrintedPagesCount(cookie, number_pages);
   OpportunisticallyCreatePrintJob(cookie);
 }
@@ -478,7 +479,8 @@
     case JobEventDetails::DOC_DONE: {
       // Don't care about the actual printing process, except on Android.
 #if defined(OS_ANDROID)
-      PdfWritingDone(number_pages_);
+      DCHECK_LE(number_pages_, kMaxPageCount);
+      PdfWritingDone(base::checked_cast<int>(number_pages_));
 #endif
       break;
     }
diff --git a/chrome/browser/printing/print_view_manager_base.h b/chrome/browser/printing/print_view_manager_base.h
index a40dfc9..3283286 100644
--- a/chrome/browser/printing/print_view_manager_base.h
+++ b/chrome/browser/printing/print_view_manager_base.h
@@ -76,7 +76,7 @@
   }
 
   // mojom::PrintManagerHost:
-  void DidGetPrintedPagesCount(int32_t cookie, int32_t number_pages) override;
+  void DidGetPrintedPagesCount(int32_t cookie, uint32_t number_pages) override;
 #if BUILDFLAG(ENABLE_TAGGED_PDF)
   void SetAccessibilityTree(
       int32_t cookie,
@@ -147,12 +147,12 @@
 // Helpers for PrintForPrintPreview();
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
   void OnPrintSettingsDone(scoped_refptr<base::RefCountedMemory> print_data,
-                           int page_count,
+                           uint32_t page_count,
                            PrinterHandler::PrintCallback callback,
                            std::unique_ptr<PrinterQuery> printer_query);
 
   void StartLocalPrintJob(scoped_refptr<base::RefCountedMemory> print_data,
-                          int page_count,
+                          uint32_t page_count,
                           int cookie,
                           PrinterHandler::PrintCallback callback);
 #endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)
diff --git a/chrome/browser/printing/printer_query.cc b/chrome/browser/printing/printer_query.cc
index fb46f27..5c35783 100644
--- a/chrome/browser/printing/printer_query.cc
+++ b/chrome/browser/printing/printer_query.cc
@@ -87,7 +87,7 @@
 }
 
 void PrinterQuery::GetSettings(GetSettingsAskParam ask_user_for_settings,
-                               int expected_page_count,
+                               uint32_t expected_page_count,
                                bool has_selection,
                                mojom::MarginType margin_type,
                                bool is_scripted,
diff --git a/chrome/browser/printing/printer_query.h b/chrome/browser/printing/printer_query.h
index 467c1c3..a9e24a7 100644
--- a/chrome/browser/printing/printer_query.h
+++ b/chrome/browser/printing/printer_query.h
@@ -51,7 +51,7 @@
   // |ask_for_user_settings| is DEFAULTS.
   // Caller has to ensure that |this| is alive until |callback| is run.
   void GetSettings(GetSettingsAskParam ask_user_for_settings,
-                   int expected_page_count,
+                   uint32_t expected_page_count,
                    bool has_selection,
                    mojom::MarginType margin_type,
                    bool is_scripted,
diff --git a/chrome/browser/printing/test_print_job.cc b/chrome/browser/printing/test_print_job.cc
index ccf0dc1..a9a5df3 100644
--- a/chrome/browser/printing/test_print_job.cc
+++ b/chrome/browser/printing/test_print_job.cc
@@ -18,7 +18,7 @@
 
 void TestPrintJob::Initialize(std::unique_ptr<PrinterQuery> query,
                               const base::string16& name,
-                              int page_count) {
+                              uint32_t page_count) {
   // Since we do not actually print in these tests, just let this get destroyed
   // when this function exits.
   std::unique_ptr<PrintJobWorker> worker = query->DetachWorker();
diff --git a/chrome/browser/printing/test_print_job.h b/chrome/browser/printing/test_print_job.h
index 47de142..45bc203 100644
--- a/chrome/browser/printing/test_print_job.h
+++ b/chrome/browser/printing/test_print_job.h
@@ -39,7 +39,7 @@
   // All remaining functions are PrintJob implementation.
   void Initialize(std::unique_ptr<PrinterQuery> query,
                   const base::string16& name,
-                  int page_count) override;
+                  uint32_t page_count) override;
 
   // Sets |job_pending_| to true.
   void StartPrinting() override;
diff --git a/chrome/browser/profiles/profile_attributes_entry.cc b/chrome/browser/profiles/profile_attributes_entry.cc
index a25a9bd..c18ba90 100644
--- a/chrome/browser/profiles/profile_attributes_entry.cc
+++ b/chrome/browser/profiles/profile_attributes_entry.cc
@@ -40,7 +40,7 @@
 #endif
 
 #if !defined(OS_ANDROID)
-#include "chrome/browser/themes/theme_properties.h"
+#include "chrome/browser/themes/theme_properties.h"  // nogncheck crbug.com/1125897
 #include "chrome/browser/ui/signin/profile_colors_util.h"
 #endif
 
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.cc b/chrome/browser/profiles/profile_avatar_icon_util.cc
index 6d88545..27c6672 100644
--- a/chrome/browser/profiles/profile_avatar_icon_util.cc
+++ b/chrome/browser/profiles/profile_avatar_icon_util.cc
@@ -44,7 +44,7 @@
 
 #if defined(OS_WIN)
 #include "chrome/browser/profiles/profile_attributes_entry.h"
-#include "chrome/grit/chrome_unscaled_resources.h"
+#include "chrome/grit/chrome_unscaled_resources.h"  // nogncheck crbug.com/1125897
 #include "ui/gfx/icon_util.h"  // For Iconutil::kLargeIconSize.
 #endif
 
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 9e827b4e..222d718 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -34,6 +34,7 @@
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/apps/platform_apps/app_load_service.h"
 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
+#include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
@@ -843,6 +844,10 @@
   if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_COPY)) {
     DCHECK(!editable);
     AppendCopyItem();
+
+    if (base::FeatureList::IsEnabled(features::kCopyLinkToText)) {
+      AppendCopyLinkToTextItem();
+    }
   }
 
   if (!content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_LINK))
@@ -1565,6 +1570,11 @@
                                   IDS_CONTENT_CONTEXT_COPY);
 }
 
+void RenderViewContextMenu::AppendCopyLinkToTextItem() {
+  menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_COPYLINKTOTEXT,
+                                  IDS_CONTENT_CONTEXT_COPYLINKTOTEXT);
+}
+
 void RenderViewContextMenu::AppendPrintItem() {
   if (GetPrefs(browser_context_)->GetBoolean(prefs::kPrintingEnabled) &&
       (params_.media_type == ContextMenuDataMediaType::kNone ||
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h
index 140c038..2099838 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -175,6 +175,7 @@
   void AppendPageItems();
   void AppendExitFullscreenItem();
   void AppendCopyItem();
+  void AppendCopyLinkToTextItem();
   void AppendPrintItem();
   void AppendMediaRouterItem();
   void AppendRotationItems();
diff --git a/chrome/browser/renderer_host/pepper/device_id_fetcher.cc b/chrome/browser/renderer_host/pepper/device_id_fetcher.cc
index 892dbd6..ca6d0ac 100644
--- a/chrome/browser/renderer_host/pepper/device_id_fetcher.cc
+++ b/chrome/browser/renderer_host/pepper/device_id_fetcher.cc
@@ -30,7 +30,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_RLZ)
-#include "rlz/lib/machine_id.h"
+#include "rlz/lib/machine_id.h"  // nogncheck crbug.com/1125897
 #endif
 
 using content::BrowserPpapiHost;
diff --git a/chrome/browser/reputation/url_elision_policy.cc b/chrome/browser/reputation/url_elision_policy.cc
index acaa6da..4d076dc 100644
--- a/chrome/browser/reputation/url_elision_policy.cc
+++ b/chrome/browser/reputation/url_elision_policy.cc
@@ -6,7 +6,6 @@
 
 #include "base/feature_list.h"
 #include "chrome/browser/reputation/local_heuristics.h"
-#include "chrome/browser/reputation/safety_tip_test_utils.h"
 #include "chrome/browser/reputation/safety_tips_config.h"
 #include "components/lookalikes/core/lookalike_url_util.h"
 #include "components/omnibox/common/omnibox_features.h"
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
index e6f5e48..332c2d5 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/navigation_manager.js
@@ -190,12 +190,11 @@
       return;
     }
     const navigator = NavigationManager.instance;
-    const baseNode = node.automationNode;
-    if (!(node instanceof BasicNode) || !baseNode) {
+    if (!(node instanceof BasicNode)) {
       navigator.setNode_(node);
       return;
     }
-    if (!SwitchAccessPredicate.isWindow(baseNode)) {
+    if (!SwitchAccessPredicate.isWindow(node.automationNode)) {
       navigator.setNode_(node);
       return;
     }
@@ -212,9 +211,7 @@
     // possible that other parts of the window are occluded, but in Chrome we
     // can't drag windows off the top of the screen.
     navigator.desktop_.hitTestWithReply(center.x, location.top, (hitNode) => {
-      if (AutomationUtil.isDescendantOf(
-              hitNode,
-              /** @type {!AutomationNode} */ (baseNode))) {
+      if (AutomationUtil.isDescendantOf(hitNode, node.automationNode)) {
         navigator.setNode_(node);
       } else if (node.isValidAndVisible()) {
         NavigationManager.tryMoving(getNext(node), getNext, startingNode);
@@ -241,11 +238,8 @@
     if (nodeIsValid && !(navigator.node_ instanceof BackButtonNode)) {
       // Our group has been invalidated. Move to navigator node to repair the
       // group stack.
-      const node = navigator.node_.automationNode;
-      if (node) {
-        navigator.moveTo_(node);
-        return;
-      }
+      navigator.moveTo_(navigator.node_.automationNode);
+      return;
     }
 
     // Make sure the menu isn't open.
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/back_button_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/back_button_node.js
index 84abd34..0725173 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/back_button_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/back_button_node.js
@@ -88,13 +88,11 @@
         true /* show */, this.group_.location);
     BackButtonNode.findAutomationNode_();
 
-    if (this.group_.automationNode) {
-      this.locationChangedHandler_ = new RepeatedEventHandler(
-          this.group_.automationNode,
-          chrome.automation.EventType.LOCATION_CHANGED,
-          () => FocusRingManager.setFocusedNode(this),
-          {exactMatch: true, allAncestors: true});
-    }
+    this.locationChangedHandler_ = new RepeatedEventHandler(
+        this.group_.automationNode,
+        chrome.automation.EventType.LOCATION_CHANGED,
+        () => FocusRingManager.setFocusedNode(this),
+        {exactMatch: true, allAncestors: true});
   }
 
   /** @override */
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node.js
index 3c2aedb..f38112f 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/basic_node.js
@@ -232,10 +232,7 @@
    * @param {!AutomationNode} baseNode
    */
   constructor(baseNode) {
-    super();
-
-    /** @private {!AutomationNode} */
-    this.baseNode_ = baseNode;
+    super(baseNode);
 
     /** @private {boolean} */
     this.invalidated_ = false;
@@ -247,13 +244,8 @@
   // ================= Getters and setters =================
 
   /** @override */
-  get automationNode() {
-    return this.baseNode_;
-  }
-
-  /** @override */
   get location() {
-    return this.baseNode_.location || super.location;
+    return this.automationNode.location || super.location;
   }
 
   // ================= General methods =================
@@ -263,38 +255,37 @@
     if (!(other instanceof BasicRootNode)) {
       return false;
     }
-
-    other = /** @type {!BasicRootNode} */ (other);
-    return super.equals(other) && this.baseNode_ === other.baseNode_;
+    return super.equals(other) && this.automationNode === other.automationNode;
   }
 
   /** @override */
   isEquivalentTo(node) {
     if (node instanceof BasicRootNode || node instanceof BasicNode) {
-      return this.baseNode_ === node.baseNode_;
+      return this.automationNode === node.automationNode;
     }
 
     if (node instanceof SAChildNode) {
       return node.isEquivalentTo(this);
     }
-    return this.baseNode_ === node;
+    return this.automationNode === node;
   }
 
   /** @override */
   isValidGroup() {
-    if (!this.baseNode_.role) {
+    if (!this.automationNode.role) {
       // If the underlying automation node has been invalidated, return false.
       return false;
     }
     return !this.invalidated_ &&
-        SwitchAccessPredicate.isVisible(this.baseNode_) && super.isValidGroup();
+        SwitchAccessPredicate.isVisible(this.automationNode) &&
+        super.isValidGroup();
   }
 
   /** @override */
   onFocus() {
     super.onFocus();
     this.childrenChangedHandler_ = new RepeatedEventHandler(
-        this.baseNode_, chrome.automation.EventType.CHILDREN_CHANGED,
+        this.automationNode, chrome.automation.EventType.CHILDREN_CHANGED,
         this.refresh.bind(this));
   }
 
@@ -398,7 +389,7 @@
    */
   static getInterestingChildren(root) {
     if (root instanceof BasicRootNode) {
-      root = root.baseNode_;
+      root = root.automationNode;
     }
 
     if (root.children.length === 0) {
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/combo_box_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/combo_box_node.js
index 7c25c03..dbed6ea 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/combo_box_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/combo_box_node.js
@@ -31,13 +31,11 @@
 
   /** @override */
   onFocus() {
-    if (this.automationNode) {
-      this.expandedChangedHandler_ = new RepeatedEventHandler(
-          this.automationNode, chrome.automation.EventType.EXPANDED_CHANGED,
-          () => this.onExpandedChanged(), {exactMatch: true});
-    }
-
     super.onFocus();
+
+    this.expandedChangedHandler_ = new RepeatedEventHandler(
+        this.automationNode, chrome.automation.EventType.EXPANDED_CHANGED,
+        () => this.onExpandedChanged(), {exactMatch: true});
     this.automationNode.focus();
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/group_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/group_node.js
index 4c0a498..08f67c77 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/group_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/group_node.js
@@ -12,13 +12,18 @@
   /**
    * @param {!Array<!SAChildNode>} children The nodes that this group contains.
    *     Should not include the back button.
+   * @param {!AutomationNode} containingNode The automation node most closely
+   * containing the children.
    * @private
    */
-  constructor(children) {
+  constructor(children, containingNode) {
     super();
 
-    /** @type {!Array<!SAChildNode>} */
+    /** @private {!Array<!SAChildNode>} */
     this.children_ = children;
+
+    /** @private {!AutomationNode} */
+    this.containingNode_ = containingNode;
   }
 
   // ================= Getters and setters =================
@@ -30,7 +35,7 @@
 
   /** @override */
   get automationNode() {
-    return null;
+    return this.containingNode_;
   }
 
   /** @override */
@@ -49,7 +54,7 @@
 
   /** @override */
   asRootNode() {
-    const root = new SARootNode();
+    const root = new SARootNode(this.containingNode_);
 
     // Make a copy of the children array.
     const children = [...this.children_];
@@ -122,9 +127,10 @@
   /**
    * Assumes nodes are visually in rows.
    * @param {!Array<!SAChildNode>} nodes
+   * @param {!AutomationNode} containingNode
    * @return {!Array<!GroupNode>}
    */
-  static separateByRow(nodes) {
+  static separateByRow(nodes, containingNode) {
     const result = [];
 
     for (let i = 0; i < nodes.length;) {
@@ -138,7 +144,7 @@
         i++;
       }
 
-      result.push(new GroupNode(children));
+      result.push(new GroupNode(children, containingNode));
     }
 
     return result;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
index 8e4958d..9798788 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
@@ -177,8 +177,8 @@
     const interestingChildren =
         root.automationNode.findAll({role: chrome.automation.RoleType.BUTTON});
     /** @type {!Array<!SAChildNode>} */
-    const children =
-        GroupNode.separateByRow(interestingChildren.map(childConstructor));
+    const children = GroupNode.separateByRow(
+        interestingChildren.map(childConstructor), root.automationNode);
 
     children.push(new BackButtonNode(root));
     root.children = children;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
index 0fde079..fdea5a0 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/switch_access_node.js
@@ -37,8 +37,8 @@
   get actions() {}
 
   /**
-   * Returns the underlying automation node, if one exists.
-   * @return {AutomationNode}
+   * The automation node that most closely contains this node.
+   * @return {!AutomationNode}
    * @abstract
    */
   get automationNode() {}
@@ -217,9 +217,8 @@
 
     let str = this.role + ' ';
 
-    const autoNode = this.automationNode;
-    if (autoNode && autoNode.name) {
-      str += 'name(' + autoNode.name + ') ';
+    if (this.automationNode.name) {
+      str += 'name(' + this.automationNode.name + ') ';
     }
 
     const loc = this.location;
@@ -251,15 +250,27 @@
  * This class represents the root node of a Switch Access traversal group.
  */
 class SARootNode {
-  constructor() {
+  /**
+   * @param {!AutomationNode} autoNode The automation node that most closely
+   *     contains all of this node's children.
+   */
+  constructor(autoNode) {
     /** @private {!Array<!SAChildNode>} */
     this.children_ = [];
+
+    /** @private {!AutomationNode} */
+    this.automationNode_ = autoNode;
   }
 
   // ================= Getters and setters =================
 
-  /** @return {AutomationNode} */
-  get automationNode() {}
+  /**
+   * @return {!AutomationNode} The automation node that most closely
+   *     contains all of this node's children.
+   */
+  get automationNode() {
+    return this.automationNode_;
+  }
 
   /** @param {!Array<!SAChildNode>} newVal */
   set children(newVal) {
@@ -402,13 +413,9 @@
    * @return {string}
    */
   debugString(wholeTree = false, prefix = '', currentNode = null) {
-    const autoNode = this.automationNode;
-    let str = 'Root: ';
-    if (autoNode && autoNode.role) {
-      str += autoNode.role + ' ';
-    }
-    if (autoNode && autoNode.name) {
-      str += 'name(' + autoNode.name + ') ';
+    let str = 'Root: ' + this.automationNode.role + ' ';
+    if (this.automationNode.name) {
+      str += 'name(' + this.automationNode.name + ') ';
     }
 
     const loc = this.location;
@@ -416,7 +423,6 @@
       str += 'loc(' + RectUtil.toString(loc) + ') ';
     }
 
-
     for (const child of this.children) {
       str += '\n' + prefix + ((child.equals(currentNode)) ? ' * ' : ' - ');
       str += child.debugString(wholeTree, prefix, currentNode);
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js
index 3ff64d7..7015510 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_predicate_test.js
@@ -448,7 +448,7 @@
         return null;
       }
     }
-    const group = new TestRoot();
+    const group = new TestRoot(t.root);
 
     assertTrue(
         SwitchAccessPredicate.isGroup(t.root, group, cache),
diff --git a/chrome/browser/resources/chromeos/crostini_installer/app.js b/chrome/browser/resources/chromeos/crostini_installer/app.js
index ff3c510..c92b178f 100644
--- a/chrome/browser/resources/chromeos/crostini_installer/app.js
+++ b/chrome/browser/resources/chromeos/crostini_installer/app.js
@@ -412,9 +412,6 @@
       case InstallerState.kInstallImageLoader:
         messageId = 'loadTerminaMessage';
         break;
-      case InstallerState.kStartConcierge:
-        messageId = 'startConciergeMessage';
-        break;
       case InstallerState.kCreateDiskImage:
         messageId = 'createDiskImageMessage';
         break;
@@ -461,9 +458,6 @@
       case InstallerError.kErrorLoadingTermina:
         messageId = 'loadTerminaError';
         break;
-      case InstallerError.kErrorStartingConcierge:
-        messageId = 'startConciergeError';
-        break;
       case InstallerError.kErrorCreatingDiskImage:
         messageId = 'createDiskImageError';
         break;
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn b/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
index c86697f7..052dbe92 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
+++ b/chrome/browser/resources/chromeos/multidevice_internals/BUILD.gn
@@ -15,6 +15,8 @@
     ":multidevice_logs_browser_proxy",
     ":multidevice_phonehub_browser_proxy",
     ":multidevice_phonehub_browser_proxy",
+    ":notification_form",
+    ":notification_manager",
     ":phone_name_form",
     ":phone_status_model_form",
     ":phonehub_tab",
@@ -22,6 +24,20 @@
   ]
 }
 
+js_library("notification_manager") {
+  deps = [
+    ":notification_form",
+    ":types",
+  ]
+}
+
+js_library("notification_form") {
+  deps = [
+    ":multidevice_phonehub_browser_proxy",
+    ":types",
+  ]
+}
+
 js_library("browser_tabs_model_form") {
   deps = [
     ":multidevice_phonehub_browser_proxy",
@@ -96,6 +112,7 @@
     ":browser_tabs_model_form",
     ":i18n_setup",
     ":multidevice_phonehub_browser_proxy",
+    ":notification_manager",
     ":phone_status_model_form",
     ":types",
     "//ui/webui/resources/js:load_time_data.m",
@@ -119,5 +136,7 @@
     "shared_style.js",
     "browser_tabs_metadata_form.js",
     "browser_tabs_model_form.js",
+    "notification_form.js",
+    "notification_manager.js",
   ]
 }
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.html b/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.html
index 11888de..ee75acd 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.html
+++ b/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.html
@@ -10,31 +10,30 @@
 </style>
 
 <div class="column">
-  <div class="label">URL: </div>
-  <cr-input value="{{url_}}" id="urlInput" auto-validate required
+  <cr-input value="{{url_}}" id="urlInput" auto-validate required label="URL: "
       error-message="This browser tab will be a nullopt if no url input">
   </cr-input>
 </div>
 <div class="column">
-  <div class="label">Title: </div>
-  <cr-input value="{{title_}}" id="titleInput" auto-validate required
+  <cr-input value="{{title_}}" label="Title:" auto-validate required
       error-message="This browser tab will be a nullopt if no title input">
   </cr-input>
 </div>
 <div class="column">
-  <div class="label">Last Accessed (ms): </div>
-  <cr-input value="{{lastAccessedTimeStamp_}}"
+  <cr-input value="{{lastAccessedTimeStamp_}}" label="Last Accessed (ms):"
       id="lastAccessedTimeStampInput" type="number" min="0"
       on-change="onLastAccessTimeStampChanged_"
       auto-validate error-message="Must be greater than 0" required>
   </cr-input>
 </div>
 <div class="column" id="faviconSelector">
-  <div class="label">Favicon selector: </div>
+  <label>Favicon Image Type</label>
   <select id="faviconList" class="md-select"
-    on-change="onFaviconSelected_">
+      on-change="onFaviconSelected_">
     <template is="dom-repeat" items="[[faviconList_]]">
-      <option>[[getFaviconTypeName_(item)]]</option>
+      <option selected="[[isEqual_(item, favicon_)]]">
+        [[getImageTypeName_(item)]]
+      </option>
     </template>
   </select>
 </div>
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.js b/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.js
index 25daee2..0565087 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/browser_tabs_metadata_form.js
@@ -8,19 +8,7 @@
 import './shared_style.js';
 
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {BrowserTabsMetadataModel, FaviconType} from './types.js';
-
-/**
- * Maps a FaviconType to its title label in the dropdown.
- * @type {!Map<FaviconType, String>}
- */
-const faviconTypeToStringMap = new Map([
-  [FaviconType.PINK, 'Pink'],
-  [FaviconType.RED, 'Red'],
-  [FaviconType.GREEN, 'Green'],
-  [FaviconType.BLUE, 'Blue'],
-  [FaviconType.YELLOW, 'Yellow'],
-]);
+import {BrowserTabsMetadataModel, ImageType, imageTypeToStringMap} from './types.js';
 
 Polymer({
   is: 'browser-tabs-metadata-form',
@@ -53,10 +41,10 @@
       value: Date.now(),
     },
 
-    /** @private{FaviconType} */
+    /** @private{ImageType} */
     favicon_: {
       type: Number,
-      value: FaviconType.PINK,
+      value: ImageType.PINK,
     },
 
     /** @private */
@@ -64,11 +52,12 @@
       type: Array,
       value: () => {
         return [
-          FaviconType.PINK,
-          FaviconType.RED,
-          FaviconType.GREEN,
-          FaviconType.BLUE,
-          FaviconType.YELLOW,
+          ImageType.NONE,
+          ImageType.PINK,
+          ImageType.RED,
+          ImageType.GREEN,
+          ImageType.BLUE,
+          ImageType.YELLOW,
         ];
       },
       readonly: true,
@@ -76,12 +65,12 @@
   },
 
   /**
-   * @param {FaviconType} faviconType
+   * @param {ImageType} faviconType
    * @return {String}
    * @private
    */
-  getFaviconTypeName_(faviconType) {
-    return faviconTypeToStringMap.get(faviconType);
+  getImageTypeName_(faviconType) {
+    return imageTypeToStringMap.get(faviconType);
   },
 
   /** @private */
@@ -114,4 +103,14 @@
 
     this.lastAccessedTimeStamp_ = Number(inputValue);
   },
+
+  /**
+   * @param {*} lhs
+   * @param {*} rhs
+   * @return {boolean}
+   * @private
+   */
+  isEqual_(lhs, rhs) {
+    return lhs === rhs;
+  },
 });
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_internals_resources.grd b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_internals_resources.grd
index cf530b5..f94b8ee 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_internals_resources.grd
+++ b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_internals_resources.grd
@@ -20,6 +20,14 @@
                file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\browser_tabs_model_form.js"
                use_base_dir="false"
                type="BINDATA"/>
+      <include name="IDR_MULTIDEVICE_INTERNALS_BROWSER_NOTIFICATION_MANAGER_JS"
+               file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\notification_manager.js"
+               use_base_dir="false"
+               type="BINDATA"/>
+      <include name="IDR_MULTIDEVICE_INTERNALS_BROWSER_NOTIFICATION_FORM_JS"
+               file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\notification_form.js"
+               use_base_dir="false"
+               type="BINDATA"/>
       <include name="IDR_MULTIDEVICE_INTERNALS_INDEX_HTML"
                file="index.html"
                type="BINDATA"/>
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
index 646bde7..6d70823 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/multidevice_phonehub_browser_proxy.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-import {BrowserTabsModel, FeatureStatus, PhoneStatusModel} from './types.js';
+import {BrowserTabsModel, FeatureStatus, Notification, PhoneStatusModel} from './types.js';
 
 /**
  * JavaScript hooks into the native WebUI handler for Phonehub tab.
@@ -50,6 +50,22 @@
   setBrowserTabs(browserTabsModel) {
     chrome.send('setBrowserTabs', [browserTabsModel]);
   }
+
+  /**
+   * Sets a notification.
+   * @param {!Notification} notification
+   */
+  setNotification(notification) {
+    chrome.send('setNotification', [notification]);
+  }
+
+  /**
+   * Removes a notification with the id |notificationId|
+   * @param {number} notificationId
+   */
+  removeNotification(notificationId) {
+    chrome.send('removeNotification', [notificationId]);
+  }
 }
 
 addSingletonGetter(MultidevicePhoneHubBrowserProxy);
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/notification_form.html b/chrome/browser/resources/chromeos/multidevice_internals/notification_form.html
new file mode 100644
index 0000000..84545ce
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_internals/notification_form.html
@@ -0,0 +1,128 @@
+<style include="cr-shared-style shared-style">
+  :host {
+    display: flex;
+    flex: 1 0 100%;
+    padding: 10px;
+    width: 100%;
+  }
+
+  :host([is-sent_]) #notificationContainer {
+    background-color: LightGreen;
+  }
+
+  :host(:not([is-sent_])) #notificationContainer {
+    box-shadow: var(--cr-elevation-3);
+  }
+
+  #notificationContainer {
+    display: flex;
+    flex: 1 0 100%;
+  }
+
+  #fields {
+    flex: 4;
+  }
+
+  .dropdown {
+    display: flex;
+    flex-direction: column;
+    padding: 5px;
+  }
+
+  cr-input {
+    padding: 5px;
+  }
+</style>
+<div id="notificationContainer">
+  <div class="column">
+    <cr-button hidden="[[notification.sent]]" id="sendBtn"
+        disabled="[[!isNotificationDataValid_]]"
+        on-click="onSetNotification_" class="internals-button">
+      <span class="emphasize">Send this notification</span>
+    </cr-button>
+    <cr-button hidden="[[!notification.sent]]" id="editBtn"
+        disabled="[[!isNotificationDataValid_]]"
+        on-click="onUpdateNotification_" class="internals-button">
+      <span class="emphasize">[[updateNotificationText_]]</span>
+    </cr-button>
+    <cr-button hidden="[[!notification.sent]]" id="removeBtn"
+        on-click="onRemoveButtonClick_" class="internals-button">
+      <span class="emphasize">Remove this notification</span>
+    </cr-button>
+  </div>
+  <div class="column" id="fields">
+    <cr-input value="{{notification.id}}" label="notification ID"
+        type="number" invalid="[[!isValidId_]]"
+        on-change="onNotificationIdChanged_"
+        auto-validate error-message="ID already used" required
+        disabled="[[notification.sent]]">
+    </cr-input>
+    <cr-input label="Visible App Name"
+        value="{{notification.appMetadata.visibleAppName}}" id="urlInput">
+    </cr-input>
+    <cr-input label="Package Name"
+        value="{{notification.appMetadata.packageName}}" id="packageName">
+    </cr-input>
+    <div class="dropdown">
+      <label>Icon Image Type</label>
+      <select id="iconImageTypeSelector" class="md-select"
+          on-change="onIconImageTypeSelected_">
+        <template is="dom-repeat" items="[[imageList_]]">
+          <option selected="[[isEqual_(item, notification.appMetadata.icon)]]">
+            [[getImageTypeName_(item)]]
+          </option>
+        </template>
+      </select>
+    </div>
+    <div class="dropdown">
+      <label>Importance</label>
+      <select id="importanceSelector" class="md-select"
+          on-change="onImportanceSelected_">
+        <template is="dom-repeat" items="[[importanceList_]]">
+          <option selected="[[isEqual_(item, notification.importance)]]">
+            [[getImportanceName_(item)]]
+          </option>
+        </template>
+      </select>
+    </div>
+    <cr-input value="{{notification.inlineReplyId}}" label="Inline reply id"
+        type="number" error-message="Inline reply ID already used"
+        on-change="onInlineReplyIdChanged_" auto-validate required
+        disabled="[[notification.sent]]" invalid="[[!isValidInlineReplyId_]]">
+    </cr-input>
+    <cr-input value="{{notification.timestamp}}" label="Timestamp (ms)" min="0"
+        id="timestampInput" type="number" on-change="onTimeStampChanged_"
+        auto-validate error-message="Must be greater than 0" required>
+    </cr-input>
+    <cr-input label="Title (Optional)" value="{{notification.title}}">
+    </cr-input>
+    <cr-input label="Text Content (Optional)"
+        value="{{notification.textContent}}" id="textContent">
+    </cr-input>
+    <div class="dropdown">
+      <label>
+        Shared Image Type (Optional)
+      </label>
+      <select id="sharedImageTypeSelector" class="md-select"
+        on-change="onSharedImageTypeSelected_">
+        <template is="dom-repeat" items="[[imageList_]]">
+          <option selected="[[isEqual_(item, notification.sharedImage)]]">
+            [[getImageTypeName_(item)]]
+          </option>
+        </template>
+      </select>
+    </div>
+    <div class="dropdown">
+      <label>Contact Image Type (Optional)</label>
+      <select id="contactImageSelector" class="md-select"
+        on-change="onContactImageTypeSelected_">
+        <template is="dom-repeat" items="[[imageList_]]">
+          <option  selected="[[isEqual_(item, notification.contactImage)]]">
+            [[getImageTypeName_(item)]]
+          </option>
+        </template>
+      </select>
+    </div>
+  </div>
+</div>
+
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/notification_form.js b/chrome/browser/resources/chromeos/multidevice_internals/notification_form.js
new file mode 100644
index 0000000..e5a40e6
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_internals/notification_form.js
@@ -0,0 +1,255 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/shared_style_css.m.js';
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
+import './shared_style.js';
+
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {MultidevicePhoneHubBrowserProxy} from './multidevice_phonehub_browser_proxy.js';
+import {ImageType, imageTypeToStringMap, Importance, importanceToString, Notification} from './types.js';
+
+Polymer({
+  is: 'notification-form',
+
+  _template: html`{__html_template__}`,
+
+  properties: {
+    /** @type{!Notification} */
+    notification: Object,
+
+    /** @private */
+    isNotificationDataValid_: {
+      type: Boolean,
+      computed: 'computeIsNotificationDataValid_(notification.*)',
+    },
+
+    /** @private */
+    isSent_: {
+      type: Boolean,
+      computed: 'computeIsSent_(notification.*)',
+      reflectToAttribute: true,
+    },
+
+    /** @private */
+    isValidId_: {
+      type: Boolean,
+      computed: 'computeIsValidId_(forbiddenIds)',
+    },
+
+    /** @private */
+    isValidInlineReplyId_: {
+      type: Boolean,
+      computed: 'computeIsValidInlineReplyId_(forbiddenInlineReplyIds)',
+    },
+
+    /** @type{!Array<number>} */
+    forbiddenIds: {
+      type: Array,
+      value: [],
+    },
+
+    /** @type{!Array<number>} */
+    forbiddenInlineReplyIds: {
+      type: Array,
+      value: [],
+    },
+
+    /** @private */
+    imageList_: {
+      type: Array,
+      value: () => {
+        return [
+          ImageType.NONE,
+          ImageType.PINK,
+          ImageType.RED,
+          ImageType.GREEN,
+          ImageType.BLUE,
+          ImageType.YELLOW,
+        ];
+      },
+      readonly: true,
+    },
+
+    /** @private */
+    importanceList_: {
+      type: Array,
+      value: () => {
+        return [
+          Importance.UNSPECIFIED,
+          Importance.NONE,
+          Importance.MIN,
+          Importance.LOW,
+          Importance.DEFAULT,
+          Importance.HIGH,
+        ];
+      },
+      readonly: true,
+    },
+
+    /** @private */
+    updateNotificationText_: {
+      type: String,
+      value: 'Update this notification',
+    },
+  },
+
+  /** @private{?MultidevicePhoneHubBrowserProxy}*/
+  browserProxy_: null,
+
+  /** @override */
+  created() {
+    this.browserProxy_ = MultidevicePhoneHubBrowserProxy.getInstance();
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeIsValidId_() {
+    return this.notification.sent ||
+        !this.forbiddenIds.includes(Number(this.notification.id));
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeIsValidInlineReplyId_() {
+    return this.notification.sent ||
+        !this.forbiddenInlineReplyIds.includes(
+            Number(this.notification.inlineReplyId));
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeIsNotificationDataValid_() {
+    // If either the notification ID or inline reply id is invalid,
+    // the notification is invalid.
+    if (!this.isValidId_ || !this.isValidInlineReplyId_) {
+      return false;
+    }
+
+    // Other required fields that need to be formatted correctly.
+    if (!this.notification.appMetadata.visibleAppName ||
+        this.notification.icon === ImageType.NONE ||
+        Number(this.notification.timestamp) < 0) {
+      return false;
+    }
+
+    // At least the title, text content, or shared image must be populated.
+    return !!this.notification.title || !!this.notification.textContent ||
+        this.notification.sharedImage !== ImageType.NONE;
+  },
+
+  /** @private */
+  computeIsSent_() {
+    return this.notification.sent;
+  },
+
+  /** @private */
+  onSetNotification_() {
+    this.browserProxy_.setNotification(this.notification);
+    this.notification.sent = true;
+    this.notifyPath('notification.sent');
+  },
+
+  /** @private */
+  onUpdateNotification_() {
+    this.onSetNotification_();
+    this.updateNotificationText_ = 'Update Sent!';
+    setTimeout(() => {
+      this.updateNotificationText_ = 'Update this notification';
+    }, 1000);
+  },
+
+  /** @private */
+  onRemoveButtonClick_() {
+    this.browserProxy_.removeNotification(this.notification.id);
+    this.fire('remove-notification');
+  },
+
+  /**
+   * @param {ImageType} imageType
+   * @return {String}
+   * @private
+   */
+  getImageTypeName_(imageType) {
+    return imageTypeToStringMap.get(imageType);
+  },
+
+  /**
+   * @param {Importance} importance
+   * @return {String}
+   * @private
+   */
+  getImportanceName_(importance) {
+    return importanceToString.get(importance);
+  },
+
+  /** @private */
+  onInlineReplyIdChanged_() {
+    //<cr-input> does not save value numerically.
+    this.notification.inlineReplyId = Number(this.notification.inlineReplyId);
+    this.notifyPath('notification.inlineReplyId');
+  },
+
+  /** @private */
+  onNotificationIdChanged_() {
+    //<cr-input> does not save value numerically.
+    this.notification.id = Number(this.notification.id);
+    this.notifyPath('notification.id');
+  },
+
+  /** @private */
+  onTimeStampChanged_() {
+    //<cr-input> does not save value numerically.
+    this.notification.timestamp = Number(this.notification.timestamp);
+    this.notifyPath('notification.timestamp');
+  },
+
+  /** @private */
+  onIconImageTypeSelected_() {
+    const select = /** @type {!HTMLSelectElement} */
+        (this.$$('#iconImageTypeSelector'));
+    this.notification.appMetadata.icon = this.imageList_[select.selectedIndex];
+  },
+
+  /** @private */
+  onSharedImageTypeSelected_() {
+    const select = /** @type {!HTMLSelectElement} */
+        (this.$$('#sharedImageTypeSelector'));
+    this.notification.sharedImage = this.imageList_[select.selectedIndex];
+    this.notifyPath('notification.sharedImage');
+  },
+
+  /** @private */
+  onContactImageTypeSelected_() {
+    const select = /** @type {!HTMLSelectElement} */
+        (this.$$('#contactImageSelector'));
+    this.notification.contactImage = this.imageList_[select.selectedIndex];
+    this.notifyPath('notification.contactImage');
+  },
+
+  /** @private */
+  onImportanceSelected_() {
+    const select = /** @type {!HTMLSelectElement} */
+        (this.$$('#importanceSelector'));
+    this.notification.importance = this.importanceList_[select.selectedIndex];
+    this.notifyPath('notification.importance');
+  },
+
+  /**
+   * @param {*} lhs
+   * @param {*} rhs
+   * @return {boolean}
+   * @private
+   */
+  isEqual_(lhs, rhs) {
+    return lhs === rhs;
+  },
+});
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.html b/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.html
new file mode 100644
index 0000000..f500cc0
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.html
@@ -0,0 +1,35 @@
+<style include="cr-shared-style shared-style">
+  :host {
+    display: flex;
+    flex: 1 0 100%;
+  }
+
+  #listContainer {
+    flex: 3;
+    height: 40vh;
+  }
+</style>
+
+<div class="column">
+  <cr-button on-click="onAddNotificationClick_" class="internals-button">
+    Add Notification
+  </cr-button>
+  <div class="label">
+    <span class="emphasize">Note:</span> A notification should include at least
+    one of Title, Text Content, and Shared Image so that it can be
+    rendered in the UI. When the notification is first sent, the notification
+    ID and Inline Reply ID will not be editable anymore. Sent notifications will
+    have a green background. To update a notification, change the fields and
+    click the Update button. To remove a notification, click the Remove button.
+  </div>
+</div>
+<div class="column" id="listContainer">
+  <template id="notificationList" is="dom-repeat"
+      items="{{notificationList_}}">
+    <notification-form class="notification" notification="{{item}}"
+        forbidden-ids="[[sentNotificationIds_]]"
+        forbidden-inline-reply-ids="[[sentInlineReplyIds_]]"
+        on-remove-notification="onRemoveNotification_">
+    </notification-form>
+  </template>
+</div>
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.js b/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.js
new file mode 100644
index 0000000..2514969
--- /dev/null
+++ b/chrome/browser/resources/chromeos/multidevice_internals/notification_manager.js
@@ -0,0 +1,114 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/shared_style_css.m.js';
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
+import './shared_style.js';
+import './notification_form.js';
+
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {ImageType, Importance, Notification} from './types.js';
+
+/**
+ * @param {number} notificationId
+ * @param {number} nextNotificationInlineReplyId
+ * @return {!Notification}
+ */
+function newNotification(notificationId, nextNotificationInlineReplyId) {
+  return {
+    sent: false,
+    id: notificationId,
+    appMetadata: {
+      visibleAppName: 'Fake visible app name',
+      packageName: 'Fake package name',
+      icon: ImageType.RED,
+    },
+    timestamp: Date.now(),
+    importance: Importance.DEFAULT,
+    inlineReplyId: nextNotificationInlineReplyId,
+    title: null,
+    textContent: null,
+    sharedImage: ImageType.NONE,
+    contactImage: ImageType.NONE,
+  };
+}
+
+Polymer({
+  is: 'notification-manager',
+
+  _template: html`{__html_template__}`,
+
+  properties: {
+    /** @private */
+    idLatest_: {
+      type: Number,
+      value: 0,
+    },
+
+    /** @private */
+    inlineReplyIdLatest_: {
+      type: Number,
+      value: 0,
+    },
+
+    /**
+     * @type {!Array<!Notification>}
+     */
+    notificationList_: {
+      type: Array,
+      value: [],
+    },
+
+    /**
+     * @type {!Array<number>}
+     */
+    sentNotificationIds_: {
+      type: Array,
+      computed: 'computeSentNotificationIds_(notificationList_.*)',
+    },
+
+    /**
+     * @type {!Array<number>}
+     */
+    sentInlineReplyIds_: {
+      type: Array,
+      computed: 'computeSentInlineReplyIds_(notificationList_.*)',
+    },
+  },
+
+  /** @private */
+  onAddNotificationClick_() {
+    this.notificationList_.unshift(
+        newNotification(this.idLatest_, this.inlineReplyIdLatest_));
+    this.idLatest_++;
+    this.inlineReplyIdLatest_++;
+    this.$.notificationList.render();
+  },
+
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onRemoveNotification_(e) {
+    const notificationEl = e.path[0];
+    const notificationIndex =
+        this.$.notificationList.indexForElement(notificationEl);
+    this.notificationList_.splice(notificationIndex, 1);
+    this.$.notificationList.render();
+  },
+
+  /** @return {!Array<number>} */
+  computeSentNotificationIds_() {
+    return this.notificationList_.filter(item => item.sent)
+        .map(item => item.id);
+  },
+
+  /** @return  {!Array<number>}*/
+  computeSentInlineReplyIds_() {
+    return this.notificationList_.filter(item => item.sent)
+        .map(item => item.inlineReplyId);
+  },
+});
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
index 067b541..7aedf6f 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
+++ b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.html
@@ -6,7 +6,7 @@
 
   .cr-row {
     display: flex;
-    flex: 0 0 100%;
+    flex: 0 1 100%;
   }
 
   #flagDisabledContainer {
@@ -32,7 +32,9 @@
       <select id="featureStatusList" class="md-select"
           on-change="onFeatureStatusSelected_">
         <template is="dom-repeat" items="[[featureStatusList_]]">
-          <option>[[getFeatureStatusName_(item)]]</option>
+          <option selected="[[isEqual_(item, featureStatus_)]]">
+            [[getFeatureStatusName_(item)]]
+          </option>
         </template>
       </select>
       <div class="cr-padded-text" hidden="[[isFeatureEnabledAndConnected_]]">
@@ -52,6 +54,9 @@
       <div class="cr-row">
         <browser-tabs-model-form></browser-tabs-model-form>
       </div>
+      <div class="cr-row">
+        <notification-manager></notification-manager>
+      </div>
     </template>
   </template>
 </template>
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
index b13e519..af341e9 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/phonehub_tab.js
@@ -10,6 +10,7 @@
 import './i18n_setup.js';
 import './phone_name_form.js';
 import './phone_status_model_form.js';
+import './notification_manager.js';
 import './shared_style.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
@@ -82,7 +83,7 @@
     /** @private {!FeatureStatus} */
     featureStatus_: {
       type: Number,
-      value: FeatureStatus.NOT_ELIGIBLE_FOR_FEATURE,
+      value: FeatureStatus.ENABLED_AND_CONNECTED,
     },
 
     /** @private */
@@ -161,4 +162,14 @@
   onPhoneHubFlagButtonClick_() {
     window.open('chrome://flags/#enable-phone-hub');
   },
+
+  /**
+   * @param {*} lhs
+   * @param {*} rhs
+   * @return {boolean}
+   * @private
+   */
+  isEqual_(lhs, rhs) {
+    return lhs === rhs;
+  },
 });
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/shared_style.html b/chrome/browser/resources/chromeos/multidevice_internals/shared_style.html
index 4974ced..19a2d71 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/shared_style.html
+++ b/chrome/browser/resources/chromeos/multidevice_internals/shared_style.html
@@ -13,6 +13,11 @@
       margin: 5px;
     }
 
+    .internals-button[disabled] {
+      background-color: LightGray;
+      color: gray;
+    }
+
     .label {
       width: 100%;
     }
diff --git a/chrome/browser/resources/chromeos/multidevice_internals/types.js b/chrome/browser/resources/chromeos/multidevice_internals/types.js
index 5f77dfa..535c945 100644
--- a/chrome/browser/resources/chromeos/multidevice_internals/types.js
+++ b/chrome/browser/resources/chromeos/multidevice_internals/types.js
@@ -88,19 +88,34 @@
 };
 
 /**
- * Numerical values should not be changed because they must stay in sync with
- * FaviconType in chromeos/components/phonehub/phone_status_model.cc.
+ * With the exception of NONE, numerical values should not be changed because
+ * they must stay in sync with ImageType in
+ * chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc.
  * @enum{number}
  */
-export const FaviconType = {
-  PINK: 0,
-  RED: 1,
-  GREEN: 2,
-  BLUE: 3,
-  YELLOW: 4,
+export const ImageType = {
+  NONE: 0,
+  PINK: 1,
+  RED: 2,
+  GREEN: 3,
+  BLUE: 4,
+  YELLOW: 5,
 };
 
 /**
+ * Maps a ImageType to its title label in the dropdown.
+ * @type {!Map<ImageType, String>}
+ */
+export const imageTypeToStringMap = new Map([
+  [ImageType.NONE, 'None'],
+  [ImageType.PINK, 'Pink'],
+  [ImageType.RED, 'Red'],
+  [ImageType.GREEN, 'Green'],
+  [ImageType.BLUE, 'Blue'],
+  [ImageType.YELLOW, 'Yellow'],
+]);
+
+/**
  * @typedef {{
  *   mobileStatus: !MobileStatus,
  *   signalStrength: !SignalStrength,
@@ -117,7 +132,7 @@
  *   url: string,
  *   title: string,
  *   lastAccessedTimeStamp: number,
- *   favicon: !FaviconType,
+ *   favicon: !ImageType,
  * }}
  */
 export let BrowserTabsMetadataModel;
@@ -130,3 +145,57 @@
  * }}
  */
 export let BrowserTabsModel;
+
+/**
+ * Numerical values should not be changed because they must stay in sync with
+ * Importance in chromeos/components/phonehub/notification.h.
+ * @enum{number}
+ */
+export const Importance = {
+  UNSPECIFIED: 0,
+  NONE: 1,
+  MIN: 2,
+  LOW: 3,
+  DEFAULT: 4,
+  HIGH: 5,
+};
+
+/**
+ * Maps an Importance to its title label in the dropdown.
+ * @type {!Map<Importance, String>}
+ */
+export const importanceToString = new Map([
+  [Importance.UNSPECIFIED, 'Unspecified'],
+  [Importance.NONE, 'None'],
+  [Importance.MIN, 'Min'],
+  [Importance.LOW, 'Low'],
+  [Importance.DEFAULT, 'Default'],
+  [Importance.HIGH, 'High'],
+]);
+
+/**
+ * @typedef {{
+ *   visibleAppName: string,
+ *   packageName: string,
+ *   icon: !ImageType,
+ * }}
+ */
+export let AppMetadata;
+
+/**
+ * With the exception of the sent property, values match with Notifications in
+ * chromeos/components/phonehub/notification.h
+ * @typedef {{
+ *   sent: boolean,
+ *   id: number,
+ *   appMetadata: !AppMetadata,
+ *   timestamp: number,
+ *   importance: !Importance,
+ *   inlineReplyId: number,
+ *   title: ?string,
+ *   textContent: ?string,
+ *   sharedImage: !ImageType,
+ *   contactImage: !ImageType,
+ * }}
+ */
+export let Notification;
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index c7563df..b68df10 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -4,85 +4,51 @@
 
 import("//chrome/common/features.gni")
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/grit/preprocess_grit.gni")
+import("//tools/grit/grit_rule.gni")
 import("//tools/polymer/html_to_js.gni")
 import("../optimize_webui.gni")
 
 if (optimize_webui) {
-  preprocess_folder = "preprocessed"
+  extensions_pak_file = "extensions_resources.pak"
+  unpak_folder = "extensions_resources.unpak"
 
   optimize_webui("build") {
     host = "extensions"
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
+    input = rebase_path("$target_gen_dir/$unpak_folder", root_build_dir)
     js_out_files = [ "extensions.rollup.js" ]
     js_module_in_files = [ "extensions.js" ]
 
     deps = [
-      ":preprocess",
-      ":preprocess_generated",
+      ":unpak",
       "../../../../ui/webui/resources:preprocess",
     ]
     excludes = [ "chrome://resources/js/cr.m.js" ]
   }
 
-  preprocess_grit("preprocess") {
-    in_folder = "./"
-    out_folder = "$target_gen_dir/$preprocess_folder"
-    in_files = [
-      "drag_and_drop_handler.js",
-      "extensions.js",
-      "item_behavior.js",
-      "item_util.js",
-      "keyboard_shortcut_delegate.js",
-      "navigation_helper.js",
-      "service.js",
-      "shortcut_util.js",
-    ]
+  unpak("unpak") {
+    pak_file = extensions_pak_file
+    out_folder = unpak_folder
 
-    if (is_chromeos) {
-      in_files += [ "kiosk_browser_proxy.js" ]
-    }
+    deps = [ ":flattened_resources" ]
   }
 
-  preprocess_grit("preprocess_generated") {
-    deps = [ ":web_components" ]
-    in_folder = target_gen_dir
-    out_folder = "$target_gen_dir/$preprocess_folder"
-    in_files = [
-      "checkup.js",
-      "code_section.js",
-      "activity_log/activity_log_history_item.js",
-      "activity_log/activity_log_history.js",
-      "activity_log/activity_log.js",
-      "activity_log/activity_log_stream_item.js",
-      "activity_log/activity_log_stream.js",
-      "detail_view.js",
-      "drop_overlay.js",
-      "error_page.js",
-      "host_permissions_toggle_list.js",
-      "icons.js",
-      "install_warnings_dialog.js",
-      "item.js",
-      "item_list.js",
-      "keyboard_shortcuts.js",
-      "load_error.js",
-      "manager.js",
-      "options_dialog.js",
-      "pack_dialog_alert.js",
-      "pack_dialog.js",
-      "runtime_host_permissions.js",
-      "runtime_hosts_dialog.js",
-      "shared_style.js",
-      "shared_vars.js",
-      "shortcut_input.js",
-      "sidebar.js",
-      "toggle_row.js",
-      "toolbar.js",
+  grit("flattened_resources") {
+    source = "extensions_resources.grd"
+
+    grit_flags = [
+      "-E",
+      "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
     ]
 
-    if (is_chromeos) {
-      in_files += [ "kiosk_dialog.js" ]
-    }
+    deps = [ ":web_components" ]
+    defines = chrome_grit_defines
+    outputs = [
+      "grit/extensions_resources.h",
+      "grit/extensions_resources_map.cc",
+      "grit/extensions_resources_map.h",
+      extensions_pak_file,
+    ]
+    output_dir = "$root_gen_dir/chrome/browser/resources/extensions"
   }
 }
 
diff --git a/chrome/browser/resources/extensions/extensions_resources.grd b/chrome/browser/resources/extensions/extensions_resources.grd
index d09f191..f149dd7 100644
--- a/chrome/browser/resources/extensions/extensions_resources.grd
+++ b/chrome/browser/resources/extensions/extensions_resources.grd
@@ -14,133 +14,144 @@
     <includes>
       <include name="IDR_EXTENSIONS_CODE_SECTION_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/code_section.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_history.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_HISTORY_ITEM_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_history_item.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_stream.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_LOG_STREAM_ITEM_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/activity_log/activity_log_stream_item.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_CHECKUP_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/checkup.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_DETAIL_VIEW_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/detail_view.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_DROP_OVERLAY_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/drop_overlay.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ERROR_PAGE_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/error_page.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_KEYBOARD_SHORTCUTS_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/keyboard_shortcuts.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
     <if expr="chromeos">
       <include name="IDR_EXTENSIONS_KIOSK_DIALOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/kiosk_dialog.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
     </if>
       <include name="IDR_EXTENSIONS_MANAGER_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/manager.js"
-               preprocess="true" use_base_dir="false" type="BINDATA" />
+               preprocess="true" use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ICONS_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/icons.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_INSTALL_WARNINGS_DIALOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/install_warnings_dialog.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ITEM_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/item.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_ITEM_LIST_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/item_list.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_LOAD_ERROR_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/load_error.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_HOST_PERMISSIONS_TOGGLE_LIST_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/host_permissions_toggle_list.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_OPTIONS_DIALOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/options_dialog.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_PACK_DIALOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/pack_dialog.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_PACK_DIALOG_ALERT_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/pack_dialog_alert.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_RUNTIME_HOST_PERMISSIONS_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/runtime_host_permissions.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_RUNTIME_HOSTS_DIALOG_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/runtime_hosts_dialog.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_SHARED_STYLE_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/shared_style.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_SHARED_VARS_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/shared_vars.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_SHORTCUT_INPUT_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/shortcut_input.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_SIDEBAR_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/sidebar.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_TOGGLE_ROW_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/toggle_row.js"
-               use_base_dir="false" type="BINDATA" />
+               use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_TOOLBAR_JS"
                file="${root_gen_dir}/chrome/browser/resources/extensions/toolbar.js"
-               preprocess="true" use_base_dir="false" type="BINDATA" />
+               preprocess="true" use_base_dir="false" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_CHECKUP_IMAGE"
-               file="checkup_image.svg" type="BINDATA" />
+               file="checkup_image.svg" compress="false" type="BINDATA" />
       <include name="IDR_EXTENSIONS_CHECKUP_IMAGE_DARK"
-               file="checkup_image_dark.svg" type="BINDATA" />
+               file="checkup_image_dark.svg" compress="false" type="BINDATA" />
     </includes>
     <structures>
       <structure name="IDR_EXTENSIONS_ITEM_BEHAVIOR_JS"
                  file="item_behavior.js"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_EXTENSIONS_HTML"
                  file="extensions.html"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_DRAG_AND_DROP_HANDLER_JS"
                  file="drag_and_drop_handler.js"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_EXTENSIONS_JS"
                  file="extensions.js"
+                 preprocess="true"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_KEYBOARD_SHORTCUT_DELEGATE_JS"
                  file="keyboard_shortcut_delegate.js"
+                 compress="false"
                  type="chrome_html" />
     <if expr="chromeos">
       <structure name="IDR_EXTENSIONS_KIOSK_BROWSER_PROXY_JS"
                  file="kiosk_browser_proxy.js"
+                 compress="false"
                  type="chrome_html" />
     </if>
       <structure name="IDR_EXTENSIONS_ITEM_UTIL_JS"
                  file="item_util.js"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_NAVIGATION_HELPER_JS"
                  file="navigation_helper.js"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_SERVICE_JS"
                  file="service.js"
+                 compress="false"
                  type="chrome_html" />
       <structure name="IDR_EXTENSIONS_SHORTCUT_UTIL_JS"
                  file="shortcut_util.js"
+                 compress="false"
                  type="chrome_html" />
     </structures>
   </release>
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 7f54cac..f4491f0 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -15,6 +15,8 @@
     "url_checker_delegate_impl.h",
   ]
 
+  public_deps = []
+
   deps = [
     "//chrome/app:generated_resources",
     "//chrome/common",
@@ -241,10 +243,12 @@
         "signature_evaluator_mac.h",
         "signature_evaluator_mac.mm",
       ]
-      deps += [
-        ":advanced_protection",
+      public_deps += [
         ":chrome_enterprise_url_lookup_service",
         ":chrome_enterprise_url_lookup_service_factory",
+      ]
+      deps += [
+        ":advanced_protection",
         "//chrome/common/safe_browsing:archive_analyzer_results",
         "//chrome/common/safe_browsing:binary_feature_extractor",
         "//chrome/common/safe_browsing:disk_image_type_sniffer_mac",
@@ -280,37 +284,43 @@
   }
 }
 
-source_set("chrome_enterprise_url_lookup_service_factory") {
-  sources = [
-    "chrome_enterprise_url_lookup_service_factory.cc",
-    "chrome_enterprise_url_lookup_service_factory.h",
-  ]
+if (safe_browsing_mode == 1) {
+  source_set("chrome_enterprise_url_lookup_service_factory") {
+    visibility = [ ":*" ]
 
-  deps = [
-    ":chrome_enterprise_url_lookup_service",
-    ":verdict_cache_manager_factory",
-    "//chrome/common",
-    "//components/keyed_service/content",
-    "//content/public/browser",
-  ]
-}
+    sources = [
+      "chrome_enterprise_url_lookup_service_factory.cc",
+      "chrome_enterprise_url_lookup_service_factory.h",
+    ]
 
-source_set("chrome_enterprise_url_lookup_service") {
-  sources = [
-    "chrome_enterprise_url_lookup_service.cc",
-    "chrome_enterprise_url_lookup_service.h",
-  ]
+    deps = [
+      ":chrome_enterprise_url_lookup_service",
+      ":verdict_cache_manager_factory",
+      "//chrome/common",
+      "//components/keyed_service/content",
+      "//content/public/browser",
+    ]
+  }
 
-  deps = [
-    "//components/prefs",
-    "//components/safe_browsing/core:csd_proto",
-    "//components/safe_browsing/core:realtimeapi_proto",
-    "//components/safe_browsing/core:verdict_cache_manager",
-    "//components/safe_browsing/core/realtime:policy_engine",
-    "//components/safe_browsing/core/realtime:url_lookup_service_base",
-    "//components/sync",
-    "//services/network/public/cpp:cpp",
-  ]
+  source_set("chrome_enterprise_url_lookup_service") {
+    visibility = [ ":*" ]
+
+    sources = [
+      "chrome_enterprise_url_lookup_service.cc",
+      "chrome_enterprise_url_lookup_service.h",
+    ]
+
+    deps = [
+      "//components/prefs",
+      "//components/safe_browsing/core:csd_proto",
+      "//components/safe_browsing/core:realtimeapi_proto",
+      "//components/safe_browsing/core:verdict_cache_manager",
+      "//components/safe_browsing/core/realtime:policy_engine",
+      "//components/safe_browsing/core/realtime:url_lookup_service_base",
+      "//components/sync",
+      "//services/network/public/cpp:cpp",
+    ]
+  }
 }
 
 source_set("url_lookup_service_factory") {
diff --git a/chrome/browser/search_engines/template_url_service_factory.cc b/chrome/browser/search_engines/template_url_service_factory.cc
index 7cd204e..b3f207c 100644
--- a/chrome/browser/search_engines/template_url_service_factory.cc
+++ b/chrome/browser/search_engines/template_url_service_factory.cc
@@ -24,7 +24,7 @@
 #include "rlz/buildflags/buildflags.h"
 
 #if BUILDFLAG(ENABLE_RLZ)
-#include "components/rlz/rlz_tracker.h"
+#include "components/rlz/rlz_tracker.h"  // nogncheck crbug.com/1125897
 #endif
 
 // static
diff --git a/chrome/browser/search_engines/ui_thread_search_terms_data.cc b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
index 5553566..bcf5c7c 100644
--- a/chrome/browser/search_engines/ui_thread_search_terms_data.cc
+++ b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
@@ -20,7 +20,7 @@
 #include "url/gurl.h"
 
 #if BUILDFLAG(ENABLE_RLZ)
-#include "components/rlz/rlz_tracker.h"
+#include "components/rlz/rlz_tracker.h"  // nogncheck crbug.com/1125897
 #endif
 
 using content::BrowserThread;
diff --git a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
index ffdb052..ca81ec5 100644
--- a/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
+++ b/chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc
@@ -40,7 +40,7 @@
 
 #if !defined(OS_ANDROID)
 #include "base/files/memory_mapped_file.h"
-#include "third_party/hunspell/google/bdict.h"
+#include "third_party/hunspell/google/bdict.h"  // nogncheck crbug.com/1125897
 #endif
 
 using content::BrowserThread;
diff --git a/chrome/browser/storage/storage_notification_service_impl.cc b/chrome/browser/storage/storage_notification_service_impl.cc
index c5255b2..f8b38745 100644
--- a/chrome/browser/storage/storage_notification_service_impl.cc
+++ b/chrome/browser/storage/storage_notification_service_impl.cc
@@ -38,8 +38,9 @@
 
 void StorageNotificationServiceImpl::MaybeShowStoragePressureNotification(
     const url::Origin origin) {
-  if (base::TimeTicks::Now() - disk_pressure_notification_last_sent_at_ <
-      GetThrottlingInterval()) {
+  if (!disk_pressure_notification_last_sent_at_.is_null() &&
+      base::TimeTicks::Now() - disk_pressure_notification_last_sent_at_ <
+          GetThrottlingInterval()) {
     return;
   }
 
diff --git a/chrome/browser/task_manager/providers/browser_process_task.cc b/chrome/browser/task_manager/providers/browser_process_task.cc
index b4d1229..630d190 100644
--- a/chrome/browser/task_manager/providers/browser_process_task.cc
+++ b/chrome/browser/task_manager/providers/browser_process_task.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/task_manager/task_manager_observer.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
-#include "third_party/sqlite/sqlite3.h"
+#include "third_party/sqlite/sqlite3.h"  // nogncheck crbug.com/1126800
 #include "ui/base/l10n/l10n_util.h"
 
 namespace task_manager {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3768988..9057998 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1541,6 +1541,7 @@
       "//components/network_session_configurator/common",
       "//components/page_load_metrics/browser",
       "//components/performance_manager:site_data_proto",
+      "//components/printing/browser",
       "//components/profile_metrics",
       "//components/safety_check",
       "//components/search_provider_logos",
@@ -1747,6 +1748,8 @@
       "app_list/search/drive_quick_access_result.h",
       "app_list/search/file_chip_result.cc",
       "app_list/search/file_chip_result.h",
+      "app_list/search/files/drive_zero_state_provider.cc",
+      "app_list/search/files/drive_zero_state_provider.h",
       "app_list/search/files/file_result.cc",
       "app_list/search/files/file_result.h",
       "app_list/search/launcher_search/launcher_search_icon_image_loader.cc",
diff --git a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc
new file mode 100644
index 0000000..a85dd90
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.cc
@@ -0,0 +1,73 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "ash/public/cpp/app_list/app_list_features.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/task_runner_util.h"
+#include "chrome/browser/chromeos/drive/drive_integration_service.h"
+#include "chrome/browser/ui/app_list/search/drive_quick_access_chip_result.h"
+#include "chrome/browser/ui/app_list/search/drive_quick_access_result.h"
+#include "chrome/browser/ui/app_list/search/search_controller.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace app_list {
+
+DriveZeroStateProvider::DriveZeroStateProvider(
+    Profile* profile,
+    SearchController* search_controller)
+    : profile_(profile),
+      drive_service_(
+          drive::DriveIntegrationServiceFactory::GetForProfile(profile)),
+      suggested_files_enabled_(app_list_features::IsSuggestedFilesEnabled()) {
+  DCHECK(profile_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+      {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+}
+
+DriveZeroStateProvider::~DriveZeroStateProvider() = default;
+
+void DriveZeroStateProvider::OnFileSystemMounted() {
+  if (have_warmed_up_cache_)
+    return;
+  have_warmed_up_cache_ = true;
+
+  // TODO(crbug.com/1034842): Query ItemSuggest. We may need to call
+  // SearchController::Start afterwards, or preferably could just publish the
+  // results for this search provider.
+}
+
+ash::AppListSearchResultType DriveZeroStateProvider::ResultType() {
+  return ash::AppListSearchResultType::kDriveQuickAccess;
+}
+
+void DriveZeroStateProvider::Start(const base::string16& query) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ClearResultsSilently();
+  if (!query.empty())
+    return;
+
+  // TODO(crbug.com/1034842): Convert results cache into search results.
+}
+
+void DriveZeroStateProvider::AppListShown() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(crbug.com/1034842): Query ItemSuggest, consider rate-limiting.
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h
new file mode 100644
index 0000000..56437a9
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/files/drive_zero_state_provider.h
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_DRIVE_ZERO_STATE_PROVIDER_H_
+#define CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_DRIVE_ZERO_STATE_PROVIDER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/drive/drive_integration_service.h"
+#include "chrome/browser/chromeos/file_manager/file_tasks_notifier.h"
+#include "chrome/browser/ui/app_list/search/search_provider.h"
+
+class Profile;
+
+namespace app_list {
+
+class SearchController;
+
+class DriveZeroStateProvider : public SearchProvider,
+                               public drive::DriveIntegrationServiceObserver {
+ public:
+  DriveZeroStateProvider(Profile* profile, SearchController* search_controller);
+  ~DriveZeroStateProvider() override;
+
+  DriveZeroStateProvider(const DriveZeroStateProvider&) = delete;
+  DriveZeroStateProvider& operator=(const DriveZeroStateProvider&) = delete;
+
+  // SearchProvider:
+  void Start(const base::string16& query) override;
+  void AppListShown() override;
+  ash::AppListSearchResultType ResultType() override;
+
+  // drive::DriveIntegrationServiceObserver:
+  void OnFileSystemMounted() override;
+
+ private:
+  Profile* const profile_;
+  drive::DriveIntegrationService* const drive_service_;
+
+  // Whether the suggested files experiment is enabled.
+  const bool suggested_files_enabled_;
+
+  // Whether we have sent at least one request to ItemSuggest to warm up the
+  // results cache.
+  bool have_warmed_up_cache_ = false;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  // Factory for general use.
+  base::WeakPtrFactory<DriveZeroStateProvider> weak_ptr_factory_{this};
+  // Factory only for weak pointers for ItemSuggest API calls. Using two
+  // factories allows in-flight API calls to be cancelled independently of other
+  // tasks by invalidating only this factory's weak pointers.
+  base::WeakPtrFactory<DriveZeroStateProvider> item_suggest_weak_ptr_factory_{
+      this};
+};
+
+}  // namespace app_list
+
+#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_FILES_DRIVE_ZERO_STATE_PROVIDER_H_
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
index c7ae485..94fe74d4 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
@@ -90,11 +90,12 @@
 }
 
 void HoldingSpaceClientImpl::UnpinItem(const HoldingSpaceItem& item) {
-  DCHECK_EQ(item.type(), HoldingSpaceItem::Type::kPinnedFile);
   const storage::FileSystemURL& file_system_url =
       file_manager::util::GetFileSystemContextForExtensionId(
           profile_, file_manager::kFileManagerAppId)
           ->CrackURL(item.file_system_url());
+  DCHECK(GetHoldingSpaceKeyedService(profile_)->ContainsPinnedFile(
+      file_system_url));
   GetHoldingSpaceKeyedService(profile_)->RemovePinnedFile(file_system_url);
 }
 
diff --git a/chrome/browser/ui/find_bar/find_backend_unittest.cc b/chrome/browser/ui/find_bar/find_backend_unittest.cc
index 94dc660..d9ba183 100644
--- a/chrome/browser/ui/find_bar/find_backend_unittest.cc
+++ b/chrome/browser/ui/find_bar/find_backend_unittest.cc
@@ -64,7 +64,7 @@
   // Start searching in the first WebContents.
   find_tab_helper->StartFinding(search_term1, true /* forward_direction */,
                                 false /* case_sensitive */,
-                                true /* find_next_if_selection_matches */);
+                                true /* find_match */);
 
   // Pre-populate string should always match between the two, but find_text
   // should not.
@@ -76,7 +76,7 @@
   // Now search in the other WebContents.
   find_tab_helper2->StartFinding(search_term2, true /* forward_direction */,
                                  false /* case_sensitive */,
-                                 true /* find_next_if_selection_matches */);
+                                 true /* find_match */);
 
   // Again, pre-populate string should always match between the two, but
   // find_text should not.
@@ -88,7 +88,7 @@
   // Search again in the first WebContents
   find_tab_helper->StartFinding(search_term3, true /* forward_direction */,
                                 false /* case_sensitive */,
-                                true /* find_next_if_selection_matches */);
+                                true /* find_match */);
 
   // The fallback search term for the first WebContents will be the original
   // search.
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.cc b/chrome/browser/ui/find_bar/find_bar_controller.cc
index ed1de9e..dc255e8 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.cc
+++ b/chrome/browser/ui/find_bar/find_bar_controller.cc
@@ -51,35 +51,31 @@
   find_bar_->SetFocusAndSelection();
 
   if (find_next) {
-    base::string16 find_text;
-
-#if defined(OS_MAC)
-    // For macOS, we always want to search for the current contents of the
-    // find bar on OS X, rather than the behavior we'd get with empty
-    // find_text (see FindBarState::GetSearchPrepopulateText).
-    find_text = find_bar_->GetFindText();
-#endif
-
-    find_tab_helper->StartFinding(find_text, forward_direction,
+    find_tab_helper->StartFinding(find_bar_->GetFindText(), forward_direction,
                                   false /* case_sensitive */,
-                                  true /* find_next_if_selection_matches */);
+                                  true /* find_match */);
     return;
   }
 
-  if (!has_user_modified_text_) {
-    base::string16 selected_text = GetSelectedText();
-    auto selected_length = selected_text.length();
-    if (selected_length > 0 && selected_length <= 250) {
-      find_bar_->SetFindTextAndSelectedRange(
-          selected_text, gfx::Range(0, selected_text.length()));
-      // Start a new find based on the selection.
-      // |find_next_if_selection_matches| is set to false so that the initial
-      // result will be the selection itself.
-      find_tab_helper->StartFinding(selected_text, true /* forward_direction */,
-                                    false /* case_sensitive */,
-                                    false /* find_next_if_selection_matches */);
-    }
+  if (has_user_modified_text_)
+    return;
+
+  base::string16 selected_text = GetSelectedText();
+  auto selected_length = selected_text.length();
+  if (selected_length > 0 && selected_length <= 250) {
+    find_bar_->SetFindTextAndSelectedRange(
+        selected_text, gfx::Range(0, selected_text.length()));
   }
+  // Since this isn't a find-next operation, we don't want to jump to any
+  // matches. Doing so could cause the page to scroll when a user is just
+  // trying to pull up the find bar — they might not even want to search for
+  // whatever is prefilled (e.g. the selected text or the global pasteboard).
+  // So we set |find_match| to false, which will set up match counts and
+  // highlighting, but not jump to any matches.
+  find_tab_helper->StartFinding(find_bar_->GetFindText(),
+                                true /* forward_direction */,
+                                false /* case_sensitive */,
+                                false /* find_match */);
 }
 
 void FindBarController::EndFindSession(
@@ -246,7 +242,8 @@
 void FindBarController::MaybeSetPrepopulateText() {
   // Having a per-tab find_string is not compatible with a global find
   // pasteboard, so we always have the same find text in all find bars. This is
-  // done through the find pasteboard mechanism, so don't set the text here.
+  // done through the find pasteboard mechanism (see FindBarPlatformHelperMac),
+  // so don't set the text here.
   if (find_bar_->HasGlobalFindPasteboard())
     return;
 
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 435307a0..a2dd01e 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -63,6 +63,11 @@
 // Enables grouping tabs together in the tab strip. https://crbug.com/905491
 const base::Feature kTabGroups{"TabGroups", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Automatically create groups for users based on domain.
+// https://crbug.com/1128703
+const base::Feature kTabGroupsAutoCreate{"TabGroupsAutoCreate",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables tab groups to be collapsed and expanded. https://crbug.com/1018230
 const base::Feature kTabGroupsCollapse{"TabGroupsCollapse",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 7cb29c7..b87fd36 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -44,6 +44,8 @@
 
 extern const base::Feature kTabGroups;
 
+extern const base::Feature kTabGroupsAutoCreate;
+
 extern const base::Feature kTabGroupsCollapse;
 
 extern const base::Feature kTabGroupsCollapseFreezing;
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index 9c0851b..c763a33 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -344,7 +344,7 @@
             sender->GetID() ==
                 VIEW_ID_FIND_IN_PAGE_NEXT_BUTTON, /* forward_direction */
             false /* case_sensitive */,
-            true /* find_next_if_selection_matches */);
+            true /* find_match */);
       }
       break;
     case VIEW_ID_FIND_IN_PAGE_CLOSE_BUTTON:
@@ -383,7 +383,7 @@
       find_tab_helper->StartFinding(
           find_string, !key_event.IsShiftDown() /* forward_direction */,
           false /* case_sensitive */,
-          true /* find_next_if_selection_matches */);
+          true /* find_match */);
     }
     return true;
   }
@@ -427,7 +427,7 @@
   if (!search_text.empty()) {
     find_tab_helper->StartFinding(search_text, true /* forward_direction */,
                                   false /* case_sensitive */,
-                                  true /* find_next_if_selection_matches */);
+                                  true /* find_match */);
   } else {
     find_tab_helper->StopFinding(find_in_page::SelectionAction::kClear);
     UpdateForResult(find_tab_helper->find_result(), base::string16());
diff --git a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
index 15ea139..4cd64ee 100644
--- a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
@@ -626,6 +626,8 @@
 
   WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
+  auto* host_view = web_contents->GetRenderWidgetHostView();
+  auto* host = host_view->GetRenderWidgetHost();
 
   WebContentsFocusChangedWatcher watcher(web_contents);
 
@@ -635,16 +637,48 @@
 
   watcher.Wait();
 
-  browser()->GetFindBarController()->Show();
+  auto* find_bar_controller = browser()->GetFindBarController();
+  find_bar_controller->Show();
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
 
   // Verify the text matches the selection
   EXPECT_EQ(ASCIIToUTF16("text"), GetFindBarText());
   find_in_page::FindNotificationDetails details = WaitForFindResult();
-  // Verify the correct match is highlighted (the one corresponding to the
-  // text that was selected). See http://crbug.com/1043550
-  EXPECT_EQ(2, details.active_match_ordinal());
+  // We don't ever want the page to (potentially) scroll just from opening the
+  // find bar, so the active match should always be 0 at this point.
+  // See http://crbug.com/1043550
+  EXPECT_EQ(0, details.active_match_ordinal());
   EXPECT_EQ(5, details.number_of_matches());
+
+  // Find the next match and verify the correct match is highlighted (the
+  // one after text that was selected).
+  find_bar_controller->Show(true /*find_next*/);
+  details = WaitForFindResult();
+  EXPECT_EQ(3, details.active_match_ordinal());
+  EXPECT_EQ(5, details.number_of_matches());
+
+  // Start a new find without a selection and verify we still get find results.
+  // See https://crbug.com/1124605
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_ESCAPE, false,
+                                              false, false, false));
+  // Wait until the focus settles.
+  content::RunUntilInputProcessed(host);
+
+  // Shift-tab back to the input box, then clear the text (and selection).
+  // Doing it this way in part because there's a bug with non-input-based
+  // selection changes not affecting GetSelectedText().
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, false,
+                                              true, false, false));
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_DELETE, false,
+                                              false, false, false));
+  content::RunUntilInputProcessed(host);
+  EXPECT_EQ(base::string16(), host_view->GetSelectedText());
+
+  find_bar_controller->Show();
+  details = WaitForFindResult();
+  EXPECT_EQ(0, details.active_match_ordinal());
+  // One less than before because we deleted the text in the input box.
+  EXPECT_EQ(4, details.number_of_matches());
 }
 
 IN_PROC_BROWSER_TEST_F(FindInPageTest, GlobalEscapeClosesFind) {
diff --git a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
index 2fb34f4..58af1cb0 100644
--- a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
+++ b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views.cc
@@ -37,7 +37,7 @@
 }
 
 views::View* JavaScriptTabModalDialogViewViews::GetInitiallyFocusedView() {
-  auto* text_box = message_box_view_->text_box();
+  auto* text_box = message_box_view_->GetVisiblePromptField();
   return text_box ? text_box : views::DialogDelegate::GetInitiallyFocusedView();
 }
 
diff --git a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
index cf28639..c14cfa0 100644
--- a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
+++ b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
 
 #include "base/metrics/histogram_functions.h"
-#include "base/timer/elapsed_timer.h"
 #include "chrome/browser/ui/webui/tab_search/tab_search_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
@@ -28,12 +27,7 @@
                    TabSearchBubbleView* parent)
       : WebView(browser_context), parent_(parent) {}
 
-  ~TabSearchWebView() override {
-    if (timer_.has_value()) {
-      UmaHistogramMediumTimes("Tabs.TabSearch.WindowDisplayedDuration",
-                              timer_->Elapsed());
-    }
-  }
+  ~TabSearchWebView() override = default;
 
   // views::WebView:
   void PreferredSizeChanged() override {
@@ -41,44 +35,8 @@
     parent_->OnWebViewSizeChanged();
   }
 
-  void OnWebContentsAttached() override { SetVisible(false); }
-
-  void ResizeDueToAutoResize(content::WebContents* web_contents,
-                             const gfx::Size& new_size) override {
-    // Don't actually do anything with this information until we have been
-    // shown. Size changes will not be honored by lower layers while we are
-    // hidden.
-    if (!GetVisible()) {
-      pending_preferred_size_ = new_size;
-      return;
-    }
-    WebView::ResizeDueToAutoResize(web_contents, new_size);
-  }
-
-  void DocumentOnLoadCompletedInMainFrame() override {
-    GetWidget()->Show();
-    GetWebContents()->Focus();
-
-    // Track window open times from when the bubble is first shown.
-    timer_ = base::ElapsedTimer();
-  }
-
-  void DidStopLoading() override {
-    if (GetVisible())
-      return;
-
-    SetVisible(true);
-    ResizeDueToAutoResize(web_contents(), pending_preferred_size_);
-  }
-
  private:
   TabSearchBubbleView* parent_;
-
-  // What we should set the preferred width to once TabSearch has loaded.
-  gfx::Size pending_preferred_size_;
-
-  // Time the Tab Search window has been open.
-  base::Optional<base::ElapsedTimer> timer_;
 };
 
 }  // namespace
@@ -103,6 +61,18 @@
   SetLayoutManager(std::make_unique<views::FillLayout>());
   web_view_->EnableSizingFromWebContents(kMinSize, kMaxSize);
   web_view_->LoadInitialURL(GURL(chrome::kChromeUITabSearchURL));
+
+  TabSearchUI* const tab_search_ui = static_cast<TabSearchUI*>(
+      web_view_->GetWebContents()->GetWebUI()->GetController());
+  tab_search_ui->AddShowUICallback(
+      base::BindOnce(&TabSearchBubbleView::ShowBubble, base::Unretained(this)));
+}
+
+TabSearchBubbleView::~TabSearchBubbleView() {
+  if (timer_.has_value()) {
+    UmaHistogramMediumTimes("Tabs.TabSearch.WindowDisplayedDuration2",
+                            timer_->Elapsed());
+  }
 }
 
 gfx::Size TabSearchBubbleView::CalculatePreferredSize() const {
@@ -121,3 +91,10 @@
 void TabSearchBubbleView::OnWebViewSizeChanged() {
   SizeToContents();
 }
+
+void TabSearchBubbleView::ShowBubble() {
+  DCHECK(GetWidget());
+  GetWidget()->Show();
+  web_view_->GetWebContents()->Focus();
+  timer_ = base::ElapsedTimer();
+}
diff --git a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
index dfb0f98..44c5edf 100644
--- a/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
+++ b/chrome/browser/ui/views/tab_search/tab_search_bubble_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
 
 #include "base/scoped_observer.h"
+#include "base/timer/elapsed_timer.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
 namespace views {
@@ -37,7 +38,7 @@
 
   TabSearchBubbleView(content::BrowserContext* browser_context,
                       views::View* anchor_view);
-  ~TabSearchBubbleView() override = default;
+  ~TabSearchBubbleView() override;
 
   // views::BubbleDialogDelegateView:
   gfx::Size CalculatePreferredSize() const override;
@@ -46,8 +47,13 @@
   void OnWebViewSizeChanged();
 
  private:
+  void ShowBubble();
+
   views::WebView* web_view_;
 
+  // Time the Tab Search window has been open.
+  base::Optional<base::ElapsedTimer> timer_;
+
   DISALLOW_COPY_AND_ASSIGN(TabSearchBubbleView);
 };
 
diff --git a/chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.cc b/chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.cc
index c5c2c9b..9a46512 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.cc
@@ -49,7 +49,6 @@
       {"errorTitle", IDS_CROSTINI_INSTALLER_ERROR_TITLE},
 
       {"loadTerminaError", IDS_CROSTINI_INSTALLER_LOAD_TERMINA_ERROR},
-      {"startConciergeError", IDS_CROSTINI_INSTALLER_START_CONCIERGE_ERROR},
       {"createDiskImageError", IDS_CROSTINI_INSTALLER_CREATE_DISK_IMAGE_ERROR},
       {"startTerminaVmError", IDS_CROSTINI_INSTALLER_START_TERMINA_VM_ERROR},
       {"startContainerError", IDS_CROSTINI_INSTALLER_START_CONTAINER_ERROR},
@@ -61,7 +60,6 @@
       {"unknownError", IDS_CROSTINI_INSTALLER_UNKNOWN_ERROR},
 
       {"loadTerminaMessage", IDS_CROSTINI_INSTALLER_LOAD_TERMINA_MESSAGE},
-      {"startConciergeMessage", IDS_CROSTINI_INSTALLER_START_CONCIERGE_MESSAGE},
       {"createDiskImageMessage",
        IDS_CROSTINI_INSTALLER_CREATE_DISK_IMAGE_MESSAGE},
       {"startTerminaVmMessage",
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
index 45ca05fd..3c77e79 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
@@ -19,32 +19,32 @@
 
 namespace {
 
-// Fake Favicon colors used for coloring the fake favicon bitmaps.
-enum class FaviconType {
-  kPink = 0,
-  kRed = 1,
-  kGreen = 2,
-  kBlue = 3,
-  kYellow = 4,
+// Fake image types used for fields that require gfx::Image().
+enum class ImageType {
+  kPink = 1,
+  kRed = 2,
+  kGreen = 3,
+  kBlue = 4,
+  kYellow = 5,
 };
 
-const SkBitmap FaviconNumToBitmap(FaviconType favicon_num) {
+const SkBitmap ImageTypeToBitmap(ImageType image_type_num) {
   SkBitmap bitmap;
   bitmap.allocN32Pixels(16, 16);
-  switch (favicon_num) {
-    case FaviconType::kPink:
+  switch (image_type_num) {
+    case ImageType::kPink:
       bitmap.eraseARGB(0, 255, 192, 203);
       break;
-    case FaviconType::kRed:
+    case ImageType::kRed:
       bitmap.eraseARGB(0, 255, 0, 0);
       break;
-    case FaviconType::kGreen:
+    case ImageType::kGreen:
       bitmap.eraseARGB(0, 0, 255, 0);
       break;
-    case FaviconType::kBlue:
+    case ImageType::kBlue:
       bitmap.eraseARGB(0, 0, 0, 255);
       break;
-    case FaviconType::kYellow:
+    case ImageType::kYellow:
       bitmap.eraseARGB(0, 255, 255, 0);
       break;
     default:
@@ -53,6 +53,24 @@
   return bitmap;
 }
 
+phonehub::Notification::AppMetadata DictToAppMetadata(
+    const base::DictionaryValue* app_metadata_dict) {
+  base::string16 visible_app_name;
+  CHECK(app_metadata_dict->GetString("visibleAppName", &visible_app_name));
+
+  std::string package_name;
+  CHECK(app_metadata_dict->GetString("packageName", &package_name));
+
+  int icon_image_type_as_int;
+  CHECK(app_metadata_dict->GetInteger("icon", &icon_image_type_as_int));
+
+  auto icon_image_type = static_cast<ImageType>(icon_image_type_as_int);
+  gfx::Image icon =
+      gfx::Image::CreateFrom1xBitmap(ImageTypeToBitmap(icon_image_type));
+  return phonehub::Notification::AppMetadata(visible_app_name, package_name,
+                                             icon);
+}
+
 base::Optional<phonehub::BrowserTabsModel::BrowserTabMetadata>
 DictToBrowserTabMetadataModel(
     const base::DictionaryValue* browser_tab_metadata) {
@@ -73,14 +91,15 @@
     return base::nullopt;
   }
 
-  int favicon_type_as_int;
-  if (!browser_tab_metadata->GetInteger("favicon", &favicon_type_as_int)) {
+  int favicon_image_type_as_int;
+  if (!browser_tab_metadata->GetInteger("favicon",
+                                        &favicon_image_type_as_int)) {
     return base::nullopt;
   }
 
-  auto favicon_type = static_cast<FaviconType>(favicon_type_as_int);
+  auto favicon_image_type = static_cast<ImageType>(favicon_image_type_as_int);
   gfx::Image favicon =
-      gfx::Image::CreateFrom1xBitmap(FaviconNumToBitmap(favicon_type));
+      gfx::Image::CreateFrom1xBitmap(ImageTypeToBitmap(favicon_image_type));
   return phonehub::BrowserTabsModel::BrowserTabMetadata(
       GURL(url), title, base::Time::FromJsTime(last_accessed_timestamp),
       favicon);
@@ -121,6 +140,16 @@
       "setBrowserTabs",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetBrowserTabs,
                           base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "setNotification",
+      base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetNotification,
+                          base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "removeNotification",
+      base::BindRepeating(&MultidevicePhoneHubHandler::HandleRemoveNotification,
+                          base::Unretained(this)));
 }
 
 void MultidevicePhoneHubHandler::EnableRealPhoneHubManager() {
@@ -278,5 +307,84 @@
     PA_LOG(VERBOSE) << "Set second most recent browser tab to" << *metadatas[0];
 }
 
+void MultidevicePhoneHubHandler::HandleSetNotification(
+    const base::ListValue* args) {
+  const base::DictionaryValue* notification_data_dict = nullptr;
+  CHECK(args->GetDictionary(0, &notification_data_dict));
+
+  int id;
+  CHECK(notification_data_dict->GetInteger("id", &id));
+
+  const base::DictionaryValue* app_metadata_dict = nullptr;
+  CHECK(
+      notification_data_dict->GetDictionary("appMetadata", &app_metadata_dict));
+  phonehub::Notification::AppMetadata app_metadata =
+      DictToAppMetadata(app_metadata_dict);
+
+  // JavaScript time stamps don't fit in int.
+  double js_timestamp;
+  CHECK(notification_data_dict->GetDouble("timestamp", &js_timestamp));
+  auto timestamp = base::Time::FromJsTime(js_timestamp);
+
+  int importance_as_int;
+  CHECK(notification_data_dict->GetInteger("importance", &importance_as_int));
+  auto importance =
+      static_cast<phonehub::Notification::Importance>(importance_as_int);
+
+  int inline_reply_id;
+  CHECK(notification_data_dict->GetInteger("inlineReplyId", &inline_reply_id));
+
+  base::Optional<base::string16> opt_title;
+  base::string16 title;
+  if (notification_data_dict->GetString("title", &title) && !title.empty()) {
+    opt_title = title;
+  }
+
+  base::Optional<base::string16> opt_text_content;
+  base::string16 text_content;
+  if (notification_data_dict->GetString("textContent", &text_content) &&
+      !text_content.empty()) {
+    opt_text_content = text_content;
+  }
+
+  base::Optional<gfx::Image> opt_shared_image;
+  int shared_image_type_as_int;
+  if (notification_data_dict->GetInteger("sharedImage",
+                                         &shared_image_type_as_int) &&
+      shared_image_type_as_int) {
+    auto shared_image_type = static_cast<ImageType>(shared_image_type_as_int);
+    opt_shared_image =
+        gfx::Image::CreateFrom1xBitmap(ImageTypeToBitmap(shared_image_type));
+  }
+
+  base::Optional<gfx::Image> opt_contact_image;
+  int contact_image_type_as_int;
+  if (notification_data_dict->GetInteger("contactImage",
+                                         &contact_image_type_as_int) &&
+      contact_image_type_as_int) {
+    auto shared_contact_image_type =
+        static_cast<ImageType>(contact_image_type_as_int);
+    opt_contact_image = gfx::Image::CreateFrom1xBitmap(
+        ImageTypeToBitmap(shared_contact_image_type));
+  }
+
+  auto notification = phonehub::Notification(
+      id, app_metadata, timestamp, importance, inline_reply_id, opt_title,
+      opt_text_content, opt_shared_image, opt_contact_image);
+
+  PA_LOG(VERBOSE) << "Set notification" << notification;
+  fake_phone_hub_manager_->fake_notification_manager()->SetNotification(
+      std::move(notification));
+}
+
+void MultidevicePhoneHubHandler::HandleRemoveNotification(
+    const base::ListValue* args) {
+  int notification_id = 0;
+  CHECK(args->GetInteger(0, &notification_id));
+  fake_phone_hub_manager_->fake_notification_manager()->RemoveNotification(
+      notification_id);
+  PA_LOG(VERBOSE) << "Removed notification with id " << notification_id;
+}
+
 }  // namespace multidevice
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
index 1ed7729..cd5acea 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
@@ -37,6 +37,8 @@
   void HandleSetFakePhoneName(const base::ListValue* args);
   void HandleSetFakePhoneStatus(const base::ListValue* args);
   void HandleSetBrowserTabs(const base::ListValue* args);
+  void HandleSetNotification(const base::ListValue* args);
+  void HandleRemoveNotification(const base::ListValue* args);
 
   std::unique_ptr<phonehub::FakePhoneHubManager> fake_phone_hub_manager_;
 };
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index cc4d6e8..cf431e2 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -18,6 +18,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -611,19 +612,19 @@
   initiator_title_ = job_title;
 }
 
-bool PrintPreviewUI::LastPageComposited(int page_number) const {
+bool PrintPreviewUI::LastPageComposited(uint32_t page_number) const {
   if (pages_to_render_.empty())
     return false;
 
   return page_number == pages_to_render_.back();
 }
 
-int PrintPreviewUI::GetPageToNupConvertIndex(int page_number) const {
+uint32_t PrintPreviewUI::GetPageToNupConvertIndex(uint32_t page_number) const {
   for (size_t index = 0; index < pages_to_render_.size(); ++index) {
     if (page_number == pages_to_render_[index])
       return index;
   }
-  return -1;
+  return kInvalidPageIndex;
 }
 
 std::vector<base::ReadOnlySharedMemoryRegion>
@@ -701,7 +702,8 @@
 void PrintPreviewUI::OnDidStartPreview(
     const mojom::DidStartPreviewParams& params,
     int request_id) {
-  DCHECK_GT(params.page_count, 0);
+  DCHECK_GT(params.page_count, 0u);
+  DCHECK_LE(params.page_count, kMaxPageCount);
   DCHECK(!params.pages_to_render.empty());
 
   pages_to_render_ = params.pages_to_render;
@@ -712,8 +714,8 @@
 
   if (g_test_delegate)
     g_test_delegate->DidGetPreviewPageCount(params.page_count);
-  handler_->SendPageCountReady(params.page_count, params.fit_to_page_scaling,
-                               request_id);
+  handler_->SendPageCountReady(base::checked_cast<int>(params.page_count),
+                               params.fit_to_page_scaling, request_id);
 }
 
 void PrintPreviewUI::OnDidGetDefaultPageLayout(
@@ -745,7 +747,7 @@
   handler_->SendPageLayoutReady(layout, has_custom_page_size_style, request_id);
 }
 
-bool PrintPreviewUI::OnPendingPreviewPage(int page_number) {
+bool PrintPreviewUI::OnPendingPreviewPage(uint32_t page_number) {
   if (pages_to_render_index_ >= pages_to_render_.size())
     return false;
 
@@ -755,16 +757,18 @@
 }
 
 void PrintPreviewUI::OnDidPreviewPage(
-    int page_number,
+    uint32_t page_number,
     scoped_refptr<base::RefCountedMemory> data,
     int preview_request_id) {
-  DCHECK_GE(page_number, 0);
+  DCHECK_NE(page_number, kInvalidPageIndex);
 
-  SetPrintPreviewDataForIndex(page_number, std::move(data));
+  SetPrintPreviewDataForIndex(base::checked_cast<int>(page_number),
+                              std::move(data));
 
   if (g_test_delegate)
     g_test_delegate->DidRenderPreviewPage(web_ui()->GetWebContents());
-  handler_->SendPagePreviewReady(page_number, *id_, preview_request_id);
+  handler_->SendPagePreviewReady(base::checked_cast<int>(page_number), *id_,
+                                 preview_request_id);
 }
 
 void PrintPreviewUI::OnPreviewDataIsAvailable(
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.h b/chrome/browser/ui/webui/print_preview/print_preview_ui.h
index cbe9622..d0e9b1f 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.h
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.h
@@ -90,11 +90,11 @@
 
   // Returns true if |page_number| is the last page in |pages_to_render_|.
   // |page_number| is a 0-based number.
-  bool LastPageComposited(int page_number) const;
+  bool LastPageComposited(uint32_t page_number) const;
 
   // Get the 0-based index of the |page_number| in |pages_to_render_|.
   // Same as above, |page_number| is a 0-based number.
-  int GetPageToNupConvertIndex(int page_number) const;
+  uint32_t GetPageToNupConvertIndex(uint32_t page_number) const;
 
   std::vector<base::ReadOnlySharedMemoryRegion> TakePagesForNupConvert();
 
@@ -153,11 +153,11 @@
   // Notifies the Web UI that the 0-based page |page_number| rendering is being
   // processed and an OnPendingPreviewPage() call is imminent. Returns whether
   // |page_number| is the expected page.
-  bool OnPendingPreviewPage(int page_number);
+  bool OnPendingPreviewPage(uint32_t page_number);
 
   // Notifies the Web UI that the 0-based page |page_number| has been rendered.
   // |preview_request_id| indicates which request resulted in this response.
-  void OnDidPreviewPage(int page_number,
+  void OnDidPreviewPage(uint32_t page_number,
                         scoped_refptr<base::RefCountedMemory> data,
                         int preview_request_id);
 
@@ -190,7 +190,7 @@
   // Allows tests to wait until the print preview dialog is loaded.
   class TestDelegate {
    public:
-    virtual void DidGetPreviewPageCount(int page_count) = 0;
+    virtual void DidGetPreviewPageCount(uint32_t page_count) = 0;
     virtual void DidRenderPreviewPage(content::WebContents* preview_dialog) = 0;
 
    protected:
@@ -279,7 +279,7 @@
   base::string16 initiator_title_;
 
   // The list of 0-based page numbers that will be rendered.
-  std::vector<int> pages_to_render_;
+  std::vector<uint32_t> pages_to_render_;
 
   // The list of pages to be converted.
   std::vector<base::ReadOnlySharedMemoryRegion> pages_for_nup_convert_;
diff --git a/chrome/browser/video_tutorials/internal/BUILD.gn b/chrome/browser/video_tutorials/internal/BUILD.gn
index 85a7fdd..b7ff98b 100644
--- a/chrome/browser/video_tutorials/internal/BUILD.gn
+++ b/chrome/browser/video_tutorials/internal/BUILD.gn
@@ -68,7 +68,10 @@
   }
 
   generate_jni("jni_headers") {
-    visibility = [ ":*" ]
+    visibility = [
+      ":*",
+      "//chrome/browser",
+    ]
 
     sources = [
       "android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialServiceFactory.java",
diff --git a/chrome/browser/web_applications/components/external_install_options.cc b/chrome/browser/web_applications/components/external_install_options.cc
index 6777c57..3adde60 100644
--- a/chrome/browser/web_applications/components/external_install_options.cc
+++ b/chrome/browser/web_applications/components/external_install_options.cc
@@ -40,7 +40,9 @@
                   is_disabled, override_previous_user_uninstall,
                   bypass_service_worker_check, require_manifest,
                   force_reinstall, wait_for_windows_closed, install_placeholder,
-                  reinstall_placeholder, uninstall_and_replace,
+                  reinstall_placeholder,
+                  load_and_await_service_worker_registration,
+                  service_worker_registration_url, uninstall_and_replace,
                   additional_search_terms) ==
          std::tie(other.install_url, other.user_display_mode,
                   other.install_source, other.add_to_applications_menu,
@@ -50,6 +52,8 @@
                   other.bypass_service_worker_check, other.require_manifest,
                   other.force_reinstall, other.wait_for_windows_closed,
                   other.install_placeholder, other.reinstall_placeholder,
+                  other.load_and_await_service_worker_registration,
+                  other.service_worker_registration_url,
                   other.uninstall_and_replace, other.additional_search_terms);
 }
 
@@ -80,6 +84,10 @@
              << install_options.install_placeholder
              << "\n reinstall_placeholder: "
              << install_options.reinstall_placeholder
+             << "\n load_and_await_service_worker_registration: "
+             << install_options.load_and_await_service_worker_registration
+             << "\n service_worker_registration_url: "
+             << install_options.service_worker_registration_url.value_or(GURL())
              << "\n uninstall_and_replace:\n  "
              << base::JoinString(install_options.uninstall_and_replace, "\n  ")
              << "\n additional_search_terms:\n "
diff --git a/chrome/browser/web_applications/components/external_install_options.h b/chrome/browser/web_applications/components/external_install_options.h
index 97172ac..b8ddf2e 100644
--- a/chrome/browser/web_applications/components/external_install_options.h
+++ b/chrome/browser/web_applications/components/external_install_options.h
@@ -103,6 +103,17 @@
   // it.
   bool reinstall_placeholder = false;
 
+  // Whether we should load |service_worker_registration_url| after successful
+  // installation to allow the site to install its service worker and set up
+  // offline caching.
+  bool load_and_await_service_worker_registration = true;
+
+  // The URL to use for service worker registration. This is
+  // configurable by sites that wish to be able to track install metrics of the
+  // install_url separate from the service worker registration step. Defaults to
+  // install_url if unset.
+  base::Optional<GURL> service_worker_registration_url;
+
   // A list of app_ids that the Web App System should attempt to uninstall and
   // replace with this app (e.g maintain shelf pins, app list positions).
   std::vector<AppId> uninstall_and_replace;
diff --git a/chrome/browser/web_applications/components/pending_app_manager.cc b/chrome/browser/web_applications/components/pending_app_manager.cc
index 4434e50..d90b2a0 100644
--- a/chrome/browser/web_applications/components/pending_app_manager.cc
+++ b/chrome/browser/web_applications/components/pending_app_manager.cc
@@ -113,6 +113,11 @@
   registration_callback_ = RegistrationCallback();
 }
 
+void PendingAppManager::SetRegistrationsCompleteCallbackForTesting(
+    base::OnceClosure callback) {
+  registrations_complete_callback_ = std::move(callback);
+}
+
 void PendingAppManager::OnRegistrationFinished(const GURL& install_url,
                                                RegistrationResultCode result) {
   if (registration_callback_)
diff --git a/chrome/browser/web_applications/components/pending_app_manager.h b/chrome/browser/web_applications/components/pending_app_manager.h
index d8e724d..accdc57 100644
--- a/chrome/browser/web_applications/components/pending_app_manager.h
+++ b/chrome/browser/web_applications/components/pending_app_manager.h
@@ -112,6 +112,7 @@
 
   void SetRegistrationCallbackForTesting(RegistrationCallback callback);
   void ClearRegistrationCallbackForTesting();
+  void SetRegistrationsCompleteCallbackForTesting(base::OnceClosure callback);
   void ClearSynchronizeRequestsForTesting();
 
   virtual void Shutdown() = 0;
@@ -128,6 +129,8 @@
   virtual void OnRegistrationFinished(const GURL& launch_url,
                                       RegistrationResultCode result);
 
+  base::OnceClosure registrations_complete_callback_;
+
  private:
   struct SynchronizeRequest {
     SynchronizeRequest(SynchronizeCallback callback, int remaining_requests);
diff --git a/chrome/browser/web_applications/external_web_app_utils.cc b/chrome/browser/web_applications/external_web_app_utils.cc
index a77e513..64d74e3 100644
--- a/chrome/browser/web_applications/external_web_app_utils.cc
+++ b/chrome/browser/web_applications/external_web_app_utils.cc
@@ -51,6 +51,22 @@
 constexpr char kLaunchContainerTab[] = "tab";
 constexpr char kLaunchContainerWindow[] = "window";
 
+// kLoadAndAwaitServiceWorkerRegistration is an optional bool that specifies
+// whether to fetch the |kServiceWorkerRegistrationUrl| after installation to
+// allow time for the app to register its service worker. This is done as a
+// second pass after install in order to not block the installation of other
+// background installed apps. No fetch is made if the service worker has already
+// been registered by the |kAppUrl|.
+// Defaults to true.
+constexpr char kLoadAndAwaitServiceWorkerRegistration[] =
+    "load_and_await_service_worker_registration";
+
+// kServiceWorkerRegistrationUrl is an optional string specifying the URL to use
+// for the above |kLoadAndAwaitServiceWorkerRegistration|.
+// Defaults to the |kAppUrl|.
+constexpr char kServiceWorkerRegistrationUrl[] =
+    "service_worker_registration_url";
+
 // kUninstallAndReplace is an optional array of strings which specifies App IDs
 // which the app is replacing. This will transfer OS attributes (e.g the source
 // app's shelf and app list positions on ChromeOS) and then uninstall the source
@@ -176,6 +192,36 @@
     return base::nullopt;
   }
 
+  bool load_and_await_service_worker_registration = true;
+  value = app_config.FindKey(kLoadAndAwaitServiceWorkerRegistration);
+  if (value) {
+    if (!value->is_bool()) {
+      LOG(ERROR) << file << " had an invalid "
+                 << kLoadAndAwaitServiceWorkerRegistration;
+      return base::nullopt;
+    }
+    load_and_await_service_worker_registration = value->GetBool();
+  }
+
+  base::Optional<GURL> service_worker_registration_url;
+  value = app_config.FindKey(kServiceWorkerRegistrationUrl);
+  if (value) {
+    if (!load_and_await_service_worker_registration) {
+      LOG(ERROR) << file << " should not specify a "
+                 << kServiceWorkerRegistrationUrl << " while "
+                 << kLoadAndAwaitServiceWorkerRegistration << " is disabled";
+    }
+    if (!value->is_string()) {
+      LOG(ERROR) << file << " had an invalid " << kServiceWorkerRegistrationUrl;
+      return base::nullopt;
+    }
+    service_worker_registration_url.emplace(value->GetString());
+    if (!service_worker_registration_url->is_valid()) {
+      LOG(ERROR) << file << " had an invalid " << kServiceWorkerRegistrationUrl;
+      return base::nullopt;
+    }
+  }
+
   value = app_config.FindKey(kUninstallAndReplace);
   std::vector<AppId> uninstall_and_replace_ids;
   if (value) {
@@ -214,6 +260,10 @@
   install_options.add_to_quick_launch_bar = create_shortcuts;
   install_options.require_manifest = true;
   install_options.uninstall_and_replace = std::move(uninstall_and_replace_ids);
+  install_options.load_and_await_service_worker_registration =
+      load_and_await_service_worker_registration;
+  install_options.service_worker_registration_url =
+      service_worker_registration_url;
   install_options.app_info_factory = std::move(app_info_factory);
 
   return install_options;
diff --git a/chrome/browser/web_applications/pending_app_manager_impl.cc b/chrome/browser/web_applications/pending_app_manager_impl.cc
index 36b8ac99..e0322c6 100644
--- a/chrome/browser/web_applications/pending_app_manager_impl.cc
+++ b/chrome/browser/web_applications/pending_app_manager_impl.cc
@@ -243,8 +243,11 @@
 }
 
 bool PendingAppManagerImpl::RunNextRegistration() {
-  if (pending_registrations_.empty())
+  if (pending_registrations_.empty()) {
+    if (registrations_complete_callback_)
+      std::move(registrations_complete_callback_).Run();
     return false;
+  }
 
   GURL url_to_check = std::move(pending_registrations_.front());
   pending_registrations_.pop_front();
@@ -275,22 +278,9 @@
 void PendingAppManagerImpl::CurrentInstallationFinished(
     const base::Optional<AppId>& app_id,
     InstallResultCode code) {
-  if (app_id && code == InstallResultCode::kSuccessNewInstall &&
-      base::FeatureList::IsEnabled(
-          features::kDesktopPWAsCacheDuringDefaultInstall)) {
-    const GURL& install_url =
-        current_install_->task->install_options().install_url;
-    bool is_local_resource =
-        install_url.scheme() == content::kChromeUIScheme ||
-        install_url.scheme() == content::kChromeUIUntrustedScheme;
-    // TODO(crbug.com/809304): Call CreateWebContentsIfNecessary() instead of
-    // checking web_contents_ once major migration of default hosted apps to web
-    // apps has completed.
-    // Temporarily using offline manifest migrations (in which |web_contents_|
-    // is nullptr) in order to avoid overwhelming migrated-to web apps with hits
-    // for service worker registrations.
-    if (!install_url.is_empty() && !is_local_resource && web_contents_)
-      pending_registrations_.push_back(install_url);
+  if (app_id && code == InstallResultCode::kSuccessNewInstall) {
+    MaybeEnqueueServiceWorkerRegistration(
+        current_install_->task->install_options());
   }
 
   // Post a task to avoid InstallableManager crashing and do so before
@@ -304,4 +294,35 @@
       .Run(task_and_callback->task->install_options().install_url, code);
 }
 
+void PendingAppManagerImpl::MaybeEnqueueServiceWorkerRegistration(
+    const ExternalInstallOptions& install_options) {
+  if (!base::FeatureList::IsEnabled(
+          features::kDesktopPWAsCacheDuringDefaultInstall)) {
+    return;
+  }
+
+  if (!install_options.load_and_await_service_worker_registration)
+    return;
+
+  // TODO(crbug.com/809304): Call CreateWebContentsIfNecessary() instead of
+  // checking web_contents_ once major migration of default hosted apps to web
+  // apps has completed.
+  // Temporarily using offline manifest migrations (in which |web_contents_|
+  // is nullptr) in order to avoid overwhelming migrated-to web apps with hits
+  // for service worker registrations.
+  if (!web_contents_)
+    return;
+
+  GURL url = install_options.service_worker_registration_url.value_or(
+      install_options.install_url);
+  if (url.is_empty())
+    return;
+  if (url.scheme() == content::kChromeUIScheme)
+    return;
+  if (url.scheme() == content::kChromeUIUntrustedScheme)
+    return;
+
+  pending_registrations_.push_back(url);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/pending_app_manager_impl.h b/chrome/browser/web_applications/pending_app_manager_impl.h
index 6b68851..1041f3e 100644
--- a/chrome/browser/web_applications/pending_app_manager_impl.h
+++ b/chrome/browser/web_applications/pending_app_manager_impl.h
@@ -87,6 +87,9 @@
   void CurrentInstallationFinished(const base::Optional<std::string>& app_id,
                                    InstallResultCode code);
 
+  void MaybeEnqueueServiceWorkerRegistration(
+      const ExternalInstallOptions& install_options);
+
   Profile* const profile_;
   ExternallyInstalledWebAppPrefs externally_installed_app_prefs_;
 
diff --git a/chrome/browser/web_applications/pending_app_manager_impl_browsertest.cc b/chrome/browser/web_applications/pending_app_manager_impl_browsertest.cc
index 8c0929c..6ea5758 100644
--- a/chrome/browser/web_applications/pending_app_manager_impl_browsertest.cc
+++ b/chrome/browser/web_applications/pending_app_manager_impl_browsertest.cc
@@ -317,6 +317,48 @@
       content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
 }
 
+IN_PROC_BROWSER_TEST_P(PendingAppManagerImplBrowserTest,
+                       RegistrationAlternateUrlSucceeds) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL install_url(
+      embedded_test_server()->GetURL("/web_apps/no_service_worker.html"));
+  GURL registration_url =
+      embedded_test_server()->GetURL("/web_apps/basic.html");
+
+  ExternalInstallOptions install_options = CreateInstallOptions(install_url);
+  install_options.bypass_service_worker_check = true;
+  install_options.service_worker_registration_url = registration_url;
+  InstallApp(std::move(install_options));
+  EXPECT_EQ(InstallResultCode::kSuccessNewInstall, result_code_.value());
+  WebAppRegistrationWaiter(&pending_app_manager())
+      .AwaitNextRegistration(registration_url,
+                             RegistrationResultCode::kSuccess);
+  CheckServiceWorkerStatus(
+      install_url,
+      content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
+}
+
+IN_PROC_BROWSER_TEST_P(PendingAppManagerImplBrowserTest, RegistrationSkipped) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Delay service worker registration to second load to simulate it not loading
+  // during the initial install pass.
+  GURL install_url(embedded_test_server()->GetURL(
+      "/web_apps/service_worker_on_second_load.html"));
+
+  ExternalInstallOptions install_options = CreateInstallOptions(install_url);
+  install_options.bypass_service_worker_check = true;
+  install_options.load_and_await_service_worker_registration = false;
+  WebAppRegistrationWaiter waiter(&pending_app_manager());
+  InstallApp(std::move(install_options));
+  waiter.AwaitRegistrationsComplete();
+
+  EXPECT_EQ(InstallResultCode::kSuccessNewInstall, result_code_.value());
+  CheckServiceWorkerStatus(install_url,
+                           content::ServiceWorkerCapability::NO_SERVICE_WORKER);
+}
+
 IN_PROC_BROWSER_TEST_P(PendingAppManagerImplBrowserTest, AlreadyRegistered) {
   ASSERT_TRUE(embedded_test_server()->Start());
   {
diff --git a/chrome/browser/web_applications/test/web_app_registration_waiter.cc b/chrome/browser/web_applications/test/web_app_registration_waiter.cc
index 1911161..251b4ed 100644
--- a/chrome/browser/web_applications/test/web_app_registration_waiter.cc
+++ b/chrome/browser/web_applications/test/web_app_registration_waiter.cc
@@ -16,6 +16,8 @@
         CHECK_EQ(code_, code);
         run_loop_.Quit();
       }));
+  manager_->SetRegistrationsCompleteCallbackForTesting(
+      complete_run_loop_.QuitClosure());
 }
 
 WebAppRegistrationWaiter::~WebAppRegistrationWaiter() {
@@ -30,4 +32,8 @@
   run_loop_.Run();
 }
 
+void WebAppRegistrationWaiter::AwaitRegistrationsComplete() {
+  complete_run_loop_.Run();
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/test/web_app_registration_waiter.h b/chrome/browser/web_applications/test/web_app_registration_waiter.h
index dab0dad..f22d452 100644
--- a/chrome/browser/web_applications/test/web_app_registration_waiter.h
+++ b/chrome/browser/web_applications/test/web_app_registration_waiter.h
@@ -19,11 +19,15 @@
   void AwaitNextRegistration(const GURL& install_url,
                              RegistrationResultCode code);
 
+  void AwaitRegistrationsComplete();
+
  private:
   PendingAppManager* const manager_;
   base::RunLoop run_loop_;
   GURL install_url_;
   RegistrationResultCode code_;
+
+  base::RunLoop complete_run_loop_;
 };
 
 }  // namespace web_app
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 5b295b3..5d81075 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1600148958-91c0cd454c0f8748eee83f19943ecba0f6b92ac0.profdata
+chrome-win64-master-1600192792-f4ca908cacd319b04516240c34a315b960a046b2.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 3d1e299e..16fe306a 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -774,6 +774,21 @@
   ]
 
   component_deps = [ "//content/public/common" ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "chrome.mojom.WebApplicationInfo"
+          cpp = "::WebApplicationInfo"
+        },
+      ]
+      traits_headers = [ "web_application_info.h" ]
+      traits_private_headers =
+          [ "web_application_info_provider_param_traits.h" ]
+      traits_deps = [ "//ipc" ]
+    },
+  ]
 }
 
 if (enable_print_preview && !is_chromeos) {
diff --git a/chrome/common/OWNERS b/chrome/common/OWNERS
index ecf27f1..5210b93 100644
--- a/chrome/common/OWNERS
+++ b/chrome/common/OWNERS
@@ -20,15 +20,9 @@
 per-file *_param_traits*.*=set noparent
 per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
 
-per-file *_type_converter*.*=set noparent
-per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
-
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
 
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
-
 # Changes to Mojo interfaces require a security review to avoid
 # introducing new sandbox escapes.
 per-file *.mojom=set noparent
diff --git a/chrome/common/search/BUILD.gn b/chrome/common/search/BUILD.gn
index d9844e7..8423b27 100644
--- a/chrome/common/search/BUILD.gn
+++ b/chrome/common/search/BUILD.gn
@@ -38,4 +38,56 @@
     "//mojo/public/mojom/base",
     "//url/mojom:url_mojom_gurl",
   ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        # TODO(dbeam): NTP -> Ntp.
+        {
+          mojom = "search.mojom.NTPLoggingEventType"
+          cpp = "::NTPLoggingEventType"
+        },
+        {
+          mojom = "search.mojom.NTPSuggestionsLoggingEventType"
+          cpp = "::NTPSuggestionsLoggingEventType"
+        },
+        {
+          mojom = "search.mojom.NTPTileImpression"
+          cpp = "::ntp_tiles::NTPTileImpression"
+        },
+        {
+          mojom = "search.mojom.OmniboxFocusState"
+          cpp = "::OmniboxFocusState"
+        },
+        {
+          mojom = "search.mojom.OmniboxFocusChangeReason"
+          cpp = "::OmniboxFocusChangeReason"
+        },
+        {
+          mojom = "search.mojom.InstantMostVisitedInfo"
+          cpp = "::InstantMostVisitedInfo"
+        },
+        {
+          mojom = "search.mojom.NtpTheme"
+          cpp = "::NtpTheme"
+        },
+        {
+          mojom = "search.mojom.SearchBoxTheme"
+          cpp = "::SearchBoxTheme"
+        },
+      ]
+      traits_headers = [
+        "//chrome/common/search/instant_types.h",
+        "//chrome/common/search/ntp_logging_events.h",
+        "//components/ntp_tiles/ntp_tile_impression.h",
+        "//components/omnibox/common/omnibox_focus_state.h",
+      ]
+      traits_private_headers =
+          [ "//chrome/common/search/instant_mojom_traits.h" ]
+      traits_deps = [
+        "//ipc",
+        "//skia",
+      ]
+    },
+  ]
 }
diff --git a/chrome/common/search/OWNERS b/chrome/common/search/OWNERS
index e62efe5..170eaf8 100644
--- a/chrome/common/search/OWNERS
+++ b/chrome/common/search/OWNERS
@@ -5,5 +5,3 @@
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/chrome/common/search/search.typemap b/chrome/common/search/search.typemap
deleted file mode 100644
index 1a19f84..0000000
--- a/chrome/common/search/search.typemap
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//chrome/common/search/search.mojom"
-public_headers = [
-  "//chrome/common/search/instant_types.h",
-  "//chrome/common/search/ntp_logging_events.h",
-  "//components/ntp_tiles/ntp_tile_impression.h",
-  "//components/omnibox/common/omnibox_focus_state.h",
-]
-traits_headers = [ "//chrome/common/search/instant_mojom_traits.h" ]
-deps = [
-  "//ipc",
-  "//skia",
-]
-type_mappings = [
-  # TODO(dbeam): NTP -> Ntp.
-  "search.mojom.NTPLoggingEventType=::NTPLoggingEventType",
-  "search.mojom.NTPSuggestionsLoggingEventType=::NTPSuggestionsLoggingEventType",
-  "search.mojom.NTPTileImpression=::ntp_tiles::NTPTileImpression",
-  "search.mojom.OmniboxFocusState=::OmniboxFocusState",
-  "search.mojom.OmniboxFocusChangeReason=::OmniboxFocusChangeReason",
-  "search.mojom.InstantMostVisitedInfo=::InstantMostVisitedInfo",
-  "search.mojom.NtpTheme=::NtpTheme",
-  "search.mojom.SearchBoxTheme=::SearchBoxTheme",
-]
diff --git a/chrome/common/web_application_info_provider.typemap b/chrome/common/web_application_info_provider.typemap
deleted file mode 100644
index 45d4ee3..0000000
--- a/chrome/common/web_application_info_provider.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//chrome/common/chrome_render_frame.mojom"
-public_headers = [ "//chrome/common/web_application_info.h" ]
-traits_headers =
-    [ "//chrome/common/web_application_info_provider_param_traits.h" ]
-public_deps = [
-  "//ipc",
-]
-
-type_mappings = [ "chrome.mojom.WebApplicationInfo=::WebApplicationInfo" ]
diff --git a/chrome/installer/util/BUILD.gn b/chrome/installer/util/BUILD.gn
index 88cc5d8..2ea9555 100644
--- a/chrome/installer/util/BUILD.gn
+++ b/chrome/installer/util/BUILD.gn