diff --git a/DEPS b/DEPS
index 1424453..cca67e30 100644
--- a/DEPS
+++ b/DEPS
@@ -102,19 +102,19 @@
   # 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': '0ba9fa0233407e3e288aeed3f296ed9c715e4ea6',
+  'skia_revision': 'fd6a52cc84364208f65f1ee52644192d6855ab0e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '781e6a98fb2b6d8430e7d2eb8ba0db3312b8f45f',
+  'v8_revision': '9b39cfe022a85a3e9bdd08d27bac7365ceb6208e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
-  'swarming_revision': '3543e21830b9549e5b70c8c49482c8c28da2ba94',
+  'swarming_revision': '281c390193ec8c02e60279f8dac1b86ac52fa4be',
   # 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': '2f3f4141066a2d28ff27647c902e42bf4ade22e0',
+  'angle_revision': '7921662045409ce4eed5016f3642a51e32400c3a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -126,7 +126,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': 'ee0fffe57e04041ba092a860418e5d4cfcbfd999',
+  'pdfium_revision': '38cb7263a0923dd5613da24b18d3d7ef052ff5e3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -162,7 +162,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': 'b26b30dc089a201e66b56c6ca9c1725d01e96fde',
+  'catapult_revision': '54e864df3f870ba7c3b9a47ce392eb1a6db57edc',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -515,7 +515,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd606e647f2a0f26879bd53261df5456885643f69',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c4db2220d369130398800e33f83735755baec860',
       'condition': 'checkout_linux',
   },
 
@@ -540,7 +540,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '621fe6f9b56a04d06b4a730cd2867d50282a6490',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '88087bad47fb55e5e783ef678faddf6174a29a13',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1007,7 +1007,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '198d637dd3e21d837fac6b3186cc6bc72e2f7219',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '28deb90728c06a35d8847d2aeda2fc1aee105c5e',
+    Var('webrtc_git') + '/src.git' + '@' + '38eac97df017c202f75331f074e5777fca4d2cbe',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1041,7 +1041,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@186537f188610462f4429f1dfa720a0d0a26d62f',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d47dffd19fcdcdcbb5a1811c5cef69066764ae79',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_field_trial_creator.cc b/android_webview/browser/aw_field_trial_creator.cc
index 3defbb6..29f17853 100644
--- a/android_webview/browser/aw_field_trial_creator.cc
+++ b/android_webview/browser/aw_field_trial_creator.cc
@@ -11,6 +11,7 @@
 
 #include "android_webview/browser/aw_metrics_service_client.h"
 #include "base/base_switches.h"
+#include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/path_service.h"
 #include "base/strings/string_split.h"
@@ -69,6 +70,22 @@
 }
 
 void AwFieldTrialCreator::SetUpFieldTrials() {
+  DoSetUpFieldTrials();
+
+  // If DoSetUpFieldTrials failed, it might have skipped creating
+  // FeatureList. If so, create a FeatureList without field trials.
+  if (!base::FeatureList::GetInstance()) {
+    const base::CommandLine* command_line =
+        base::CommandLine::ForCurrentProcess();
+    auto feature_list = std::make_unique<base::FeatureList>();
+    feature_list->InitializeFromCommandLine(
+        command_line->GetSwitchValueASCII(switches::kEnableFeatures),
+        command_line->GetSwitchValueASCII(switches::kDisableFeatures));
+    base::FeatureList::SetInstance(std::move(feature_list));
+  }
+}
+
+void AwFieldTrialCreator::DoSetUpFieldTrials() {
   // If the client ID isn't available yet, don't delay startup by creating it.
   // Instead, variations will be disabled for this run.
   std::string client_id;
diff --git a/android_webview/browser/aw_field_trial_creator.h b/android_webview/browser/aw_field_trial_creator.h
index 445b954d..1b78fd67 100644
--- a/android_webview/browser/aw_field_trial_creator.h
+++ b/android_webview/browser/aw_field_trial_creator.h
@@ -31,6 +31,8 @@
   void SetUpFieldTrials();
 
  private:
+  void DoSetUpFieldTrials();
+
   // A/B testing infrastructure for the entire application. empty until
   // |SetupFieldTrials()| is called.
   std::unique_ptr<base::FieldTrialList> field_trial_list_;
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 87c9ea4..a5d93b42 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -107,6 +107,8 @@
     "assistant/ui/assistant_bubble.h",
     "assistant/ui/assistant_bubble_view.cc",
     "assistant/ui/assistant_bubble_view.h",
+    "assistant/ui/assistant_main_view.cc",
+    "assistant/ui/assistant_main_view.h",
     "assistant/ui/assistant_ui_constants.h",
     "assistant/ui/caption_bar.cc",
     "assistant/ui/caption_bar.h",
@@ -116,6 +118,8 @@
     "assistant/ui/dialog_plate/dialog_plate.h",
     "assistant/ui/logo_view/base_logo_view.cc",
     "assistant/ui/logo_view/base_logo_view.h",
+    "assistant/ui/main_stage/assistant_query_view.cc",
+    "assistant/ui/main_stage/assistant_query_view.h",
     "assistant/ui/main_stage/assistant_text_element_view.cc",
     "assistant/ui/main_stage/assistant_text_element_view.h",
     "assistant/ui/main_stage/ui_element_container_view.cc",
diff --git a/ash/assistant/ui/assistant_bubble.cc b/ash/assistant/ui/assistant_bubble.cc
index f42fd3277..0cb031e 100644
--- a/ash/assistant/ui/assistant_bubble.cc
+++ b/ash/assistant/ui/assistant_bubble.cc
@@ -4,110 +4,11 @@
 
 #include "ash/assistant/ui/assistant_bubble.h"
 
-#include <memory>
-
 #include "ash/assistant/assistant_controller.h"
 #include "ash/assistant/ui/assistant_bubble_view.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/display/display.h"
-#include "ui/display/screen.h"
-#include "ui/views/bubble/bubble_dialog_delegate.h"
-#include "ui/views/bubble/bubble_frame_view.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/view.h"
-#include "ui/wm/core/shadow_types.h"
 
 namespace ash {
 
-namespace {
-
-// Appearance.
-constexpr SkColor kBackgroundColor = SK_ColorWHITE;
-constexpr int kCornerRadiusDip = 20;
-constexpr int kMarginDip = 8;
-
-// AssistantContainerView ------------------------------------------------------
-
-class AssistantContainerView : public views::BubbleDialogDelegateView {
- public:
-  explicit AssistantContainerView(AssistantController* assistant_controller)
-      : assistant_controller_(assistant_controller) {
-    set_accept_events(true);
-    SetAnchor();
-    set_arrow(views::BubbleBorder::Arrow::BOTTOM_CENTER);
-    set_close_on_deactivate(false);
-    set_color(kBackgroundColor);
-    set_margins(gfx::Insets());
-    set_shadow(views::BubbleBorder::Shadow::NO_ASSETS);
-    set_title_margins(gfx::Insets());
-
-    views::BubbleDialogDelegateView::CreateBubble(this);
-
-    // These attributes can only be set after bubble creation:
-    GetBubbleFrameView()->bubble_border()->SetCornerRadius(kCornerRadiusDip);
-    SetAlignment(
-        views::BubbleBorder::BubbleAlignment::ALIGN_EDGE_TO_ANCHOR_EDGE);
-    SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
-  }
-
-  ~AssistantContainerView() override = default;
-
-  // views::BubbleDialogDelegateView:
-  void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
-                                views::Widget* widget) const override {
-    params->corner_radius = kCornerRadiusDip;
-    params->keep_on_top = true;
-    params->shadow_type = views::Widget::InitParams::SHADOW_TYPE_DROP;
-    params->shadow_elevation = wm::kShadowElevationActiveWindow;
-  }
-
-  void Init() override { InitLayout(); }
-
-  void ChildPreferredSizeChanged(views::View* child) override {
-    SizeToContents();
-  }
-
-  int GetDialogButtons() const override { return ui::DIALOG_BUTTON_NONE; }
-
-  void RequestFocus() override {
-    if (assistant_bubble_view_)
-      assistant_bubble_view_->RequestFocus();
-  }
-
- private:
-  void InitLayout() {
-    SetLayoutManager(std::make_unique<views::FillLayout>());
-
-    assistant_bubble_view_ = new AssistantBubbleView(assistant_controller_);
-    AddChildView(assistant_bubble_view_);
-  }
-
-  void SetAnchor() {
-    // TODO(dmblack): Handle multiple displays, dynamic shelf repositioning, and
-    // any other corner cases.
-    // Anchors to bottom center of primary display's work area.
-    display::Display primary_display =
-        display::Screen::GetScreen()->GetPrimaryDisplay();
-
-    gfx::Rect work_area = primary_display.work_area();
-    gfx::Rect anchor = gfx::Rect(work_area.x(), work_area.bottom() - kMarginDip,
-                                 work_area.width(), 0);
-
-    SetAnchorRect(anchor);
-  }
-
-  AssistantController* const assistant_controller_;  // Owned by Shell.
-
-  // Owned by view hierarchy.
-  AssistantBubbleView* assistant_bubble_view_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(AssistantContainerView);
-};
-
-}  // namespace
-
-// AssistantBubble -------------------------------------------------------------
-
 AssistantBubble::AssistantBubble(AssistantController* assistant_controller)
     : assistant_controller_(assistant_controller) {
   assistant_controller_->AddInteractionModelObserver(this);
@@ -116,14 +17,14 @@
 AssistantBubble::~AssistantBubble() {
   assistant_controller_->RemoveInteractionModelObserver(this);
 
-  if (container_view_)
-    container_view_->GetWidget()->RemoveObserver(this);
+  if (bubble_view_)
+    bubble_view_->GetWidget()->RemoveObserver(this);
 }
 
 void AssistantBubble::OnWidgetActivationChanged(views::Widget* widget,
                                                 bool active) {
   if (active)
-    container_view_->RequestFocus();
+    bubble_view_->RequestFocus();
 }
 
 void AssistantBubble::OnWidgetDestroying(views::Widget* widget) {
@@ -133,8 +34,8 @@
   // widget visibility state and the underlying interaction model state.
   assistant_controller_->StopInteraction();
 
-  container_view_->GetWidget()->RemoveObserver(this);
-  container_view_ = nullptr;
+  bubble_view_->GetWidget()->RemoveObserver(this);
+  bubble_view_ = nullptr;
 }
 
 void AssistantBubble::OnInteractionStateChanged(
@@ -150,20 +51,20 @@
 }
 
 bool AssistantBubble::IsVisible() const {
-  return container_view_ && container_view_->GetWidget()->IsVisible();
+  return bubble_view_ && bubble_view_->GetWidget()->IsVisible();
 }
 
 void AssistantBubble::Show() {
-  if (!container_view_) {
-    container_view_ = new AssistantContainerView(assistant_controller_);
-    container_view_->GetWidget()->AddObserver(this);
+  if (!bubble_view_) {
+    bubble_view_ = new AssistantBubbleView(assistant_controller_);
+    bubble_view_->GetWidget()->AddObserver(this);
   }
-  container_view_->GetWidget()->Show();
+  bubble_view_->GetWidget()->Show();
 }
 
 void AssistantBubble::Dismiss() {
-  if (container_view_)
-    container_view_->GetWidget()->Hide();
+  if (bubble_view_)
+    bubble_view_->GetWidget()->Hide();
 }
 
 }  // namespace ash
diff --git a/ash/assistant/ui/assistant_bubble.h b/ash/assistant/ui/assistant_bubble.h
index f5e4083..958adbb 100644
--- a/ash/assistant/ui/assistant_bubble.h
+++ b/ash/assistant/ui/assistant_bubble.h
@@ -16,12 +16,9 @@
 
 namespace ash {
 
+class AssistantBubbleView;
 class AssistantController;
 
-namespace {
-class AssistantContainerView;
-}  // namespace
-
 class ASH_EXPORT AssistantBubble : public views::WidgetObserver,
                                    public AssistantInteractionModelObserver {
  public:
@@ -44,8 +41,7 @@
 
   AssistantController* const assistant_controller_;  // Owned by Shell.
 
-  // Owned by view hierarchy.
-  AssistantContainerView* container_view_ = nullptr;
+  AssistantBubbleView* bubble_view_ = nullptr;  // Owned by view hierarchy.
 
   DISALLOW_COPY_AND_ASSIGN(AssistantBubble);
 };
diff --git a/ash/assistant/ui/assistant_bubble_view.cc b/ash/assistant/ui/assistant_bubble_view.cc
index e56d9c4..29427f5 100644
--- a/ash/assistant/ui/assistant_bubble_view.cc
+++ b/ash/assistant/ui/assistant_bubble_view.cc
@@ -7,345 +7,90 @@
 #include <memory>
 
 #include "ash/assistant/assistant_controller.h"
-#include "ash/assistant/model/assistant_interaction_model.h"
-#include "ash/assistant/model/assistant_query.h"
-#include "ash/assistant/ui/assistant_ui_constants.h"
-#include "ash/assistant/ui/caption_bar.h"
-#include "ash/assistant/ui/dialog_plate/dialog_plate.h"
-#include "ash/assistant/ui/main_stage/ui_element_container_view.h"
-#include "ash/assistant/ui/suggestion_container_view.h"
-#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/assistant/ui/assistant_main_view.h"
 #include "base/strings/utf_string_conversions.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/gfx/render_text.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/layout/box_layout.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/views/bubble/bubble_dialog_delegate.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/wm/core/shadow_types.h"
 
 namespace ash {
 
 namespace {
 
 // Appearance.
-constexpr int kIconSizeDip = 32;
-constexpr int kMinHeightDip = 200;
-constexpr int kMaxHeightDip = 640;
-
-// TODO(b/77638210): Replace with localized resource strings.
-constexpr char kDefaultPrompt[] = "Hi, how can I help?";
-constexpr char kStylusPrompt[] = "Draw with your stylus to select";
-
-// TODO(dmblack): Try to use existing StyledLabel class.
-// InteractionLabel ------------------------------------------------------------
-
-class InteractionLabel : public views::View {
- public:
-  explicit InteractionLabel(
-      const AssistantInteractionModel* assistant_interaction_model)
-      : assistant_interaction_model_(assistant_interaction_model),
-        render_text_(gfx::RenderText::CreateHarfBuzzInstance()) {
-    render_text_->SetFontList(render_text_->font_list().DeriveWithSizeDelta(4));
-    render_text_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
-    render_text_->SetMultiline(true);
-    ClearQuery();
-  }
-
-  ~InteractionLabel() override = default;
-
-  // views::View:
-  int GetHeightForWidth(int width) const override {
-    if (width == 0)
-      return 0;
-
-    // Cache original |display_rect|.
-    const gfx::Rect display_rect = render_text_->display_rect();
-
-    // Measure |height| for |width|.
-    render_text_->SetDisplayRect(gfx::Rect(width, 0));
-    int height = render_text_->GetStringSize().height();
-
-    // Restore original |display_rect|.
-    render_text_->SetDisplayRect(display_rect);
-
-    return height;
-  }
-
-  void OnPaint(gfx::Canvas* canvas) override {
-    views::View::OnPaint(canvas);
-    render_text_->Draw(canvas);
-  }
-
-  void SetQuery(const AssistantQuery& query) {
-    render_text_->SetColor(kTextColorPrimary);
-
-    // Empty query.
-    if (query.Empty()) {
-      render_text_->SetText(GetPrompt());
-    } else {
-      // Populated query.
-      switch (query.type()) {
-        case AssistantQueryType::kText:
-          SetTextQuery(static_cast<const AssistantTextQuery&>(query));
-          break;
-        case AssistantQueryType::kVoice:
-          SetVoiceQuery(static_cast<const AssistantVoiceQuery&>(query));
-          break;
-        case AssistantQueryType::kEmpty:
-          // Empty queries are already handled.
-          NOTREACHED();
-          break;
-      }
-    }
-    PreferredSizeChanged();
-    SchedulePaint();
-  }
-
-  void ClearQuery() { SetQuery(AssistantEmptyQuery()); }
-
- protected:
-  // views::View:
-  gfx::Size CalculatePreferredSize() const override {
-    return render_text_->GetStringSize();
-  }
-
-  void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
-    render_text_->SetDisplayRect(GetContentsBounds());
-  }
-
- private:
-  base::string16 GetPrompt() {
-    switch (assistant_interaction_model_->input_modality()) {
-      case InputModality::kStylus:
-        return base::UTF8ToUTF16(kStylusPrompt);
-      case InputModality::kKeyboard:  // fall through
-      case InputModality::kVoice:
-        return base::UTF8ToUTF16(kDefaultPrompt);
-    }
-  }
-
-  void SetTextQuery(const AssistantTextQuery& query) {
-    render_text_->SetText(base::UTF8ToUTF16(query.text()));
-  }
-
-  void SetVoiceQuery(const AssistantVoiceQuery& query) {
-    const base::string16& high_confidence_speech =
-        base::UTF8ToUTF16(query.high_confidence_speech());
-
-    render_text_->SetText(high_confidence_speech);
-
-    // We render low confidence speech in a different color than high
-    // confidence speech for emphasis.
-    if (!query.low_confidence_speech().empty()) {
-      const base::string16& low_confidence_speech =
-          base::UTF8ToUTF16(query.low_confidence_speech());
-
-      render_text_->AppendText(low_confidence_speech);
-      render_text_->ApplyColor(kTextColorHint,
-                               gfx::Range(high_confidence_speech.length(),
-                                          high_confidence_speech.length() +
-                                              low_confidence_speech.length()));
-    }
-  }
-
-  // Owned by AssistantController.
-  const AssistantInteractionModel* const assistant_interaction_model_;
-  std::unique_ptr<gfx::RenderText> render_text_;
-
-  DISALLOW_COPY_AND_ASSIGN(InteractionLabel);
-};
-
-// InteractionContainer --------------------------------------------------------
-
-class InteractionContainer : public views::View {
- public:
-  explicit InteractionContainer(
-      const AssistantInteractionModel* assistant_interaction_model)
-      : interaction_label_(new InteractionLabel(assistant_interaction_model)) {
-    InitLayout();
-  }
-
-  ~InteractionContainer() override = default;
-
-  // views::View:
-  void ChildPreferredSizeChanged(views::View* child) override {
-    PreferredSizeChanged();
-  }
-
-  void SetQuery(const AssistantQuery& query) {
-    interaction_label_->SetQuery(query);
-  }
-
-  void ClearQuery() { interaction_label_->ClearQuery(); }
-
- private:
-  void InitLayout() {
-    views::BoxLayout* layout =
-        SetLayoutManager(std::make_unique<views::BoxLayout>(
-            views::BoxLayout::Orientation::kHorizontal,
-            gfx::Insets(0, kPaddingDip), 2 * kSpacingDip));
-
-    layout->set_cross_axis_alignment(
-        views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER);
-
-    // Icon.
-    views::ImageView* icon_view = new views::ImageView();
-    icon_view->SetImage(gfx::CreateVectorIcon(kAssistantIcon, kIconSizeDip));
-    icon_view->SetImageSize(gfx::Size(kIconSizeDip, kIconSizeDip));
-    icon_view->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
-    AddChildView(icon_view);
-
-    // Interaction label.
-    AddChildView(interaction_label_);
-
-    layout->SetFlexForView(interaction_label_, 1);
-  }
-
-  InteractionLabel* interaction_label_;  // Owned by view hierarchy.
-
-  DISALLOW_COPY_AND_ASSIGN(InteractionContainer);
-};
+constexpr SkColor kBackgroundColor = SK_ColorWHITE;
+constexpr int kCornerRadiusDip = 20;
+constexpr int kMarginDip = 8;
 
 }  // namespace
 
-// AssistantBubbleView ---------------------------------------------------------
-
 AssistantBubbleView::AssistantBubbleView(
     AssistantController* assistant_controller)
-    : assistant_controller_(assistant_controller),
-      caption_bar_(new CaptionBar()),
-      interaction_container_(
-          new InteractionContainer(assistant_controller->interaction_model())),
-      ui_element_container_(new UiElementContainerView(assistant_controller)),
-      suggestions_container_(new SuggestionContainerView(assistant_controller)),
-      dialog_plate_(new DialogPlate(assistant_controller)),
-      min_height_dip_(kMinHeightDip) {
-  InitLayout();
+    : assistant_controller_(assistant_controller) {
+  set_accept_events(true);
+  SetAnchor();
+  set_arrow(views::BubbleBorder::Arrow::BOTTOM_CENTER);
+  set_close_on_deactivate(false);
+  set_color(kBackgroundColor);
+  set_margins(gfx::Insets());
+  set_shadow(views::BubbleBorder::Shadow::NO_ASSETS);
+  set_title_margins(gfx::Insets());
 
-  // Observe changes to interaction model.
-  assistant_controller_->AddInteractionModelObserver(this);
+  views::BubbleDialogDelegateView::CreateBubble(this);
+
+  // These attributes can only be set after bubble creation:
+  GetBubbleFrameView()->bubble_border()->SetCornerRadius(kCornerRadiusDip);
+  SetAlignment(views::BubbleBorder::BubbleAlignment::ALIGN_EDGE_TO_ANCHOR_EDGE);
+  SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
 }
 
-AssistantBubbleView::~AssistantBubbleView() {
-  assistant_controller_->RemoveInteractionModelObserver(this);
-}
-
-gfx::Size AssistantBubbleView::CalculatePreferredSize() const {
-  int preferred_height = GetHeightForWidth(kPreferredWidthDip);
-
-  // When not using stylus input modality:
-  // |min_height_dip_| <= preferred_height <= |kMaxHeightDip|.
-  if (assistant_controller_->interaction_model()->input_modality() !=
-      InputModality::kStylus) {
-    preferred_height =
-        std::min(std::max(preferred_height, min_height_dip_), kMaxHeightDip);
-  }
-
-  return gfx::Size(kPreferredWidthDip, preferred_height);
-}
-
-void AssistantBubbleView::OnBoundsChanged(const gfx::Rect& prev_bounds) {
-  // Until Assistant UI is hidden, the view may grow in height but not shrink.
-  // The exception to this rule is if using stylus input modality.
-  min_height_dip_ = std::max(min_height_dip_, height());
-}
+AssistantBubbleView::~AssistantBubbleView() = default;
 
 void AssistantBubbleView::ChildPreferredSizeChanged(views::View* child) {
-  PreferredSizeChanged();
-
-  // We force a layout here because, though we are receiving a
-  // ChildPreferredSizeChanged event, it may be that the
-  // |ui_element_container_|'s bounds will not actually change due to the height
-  // restrictions imposed by AssistantBubbleView. When this is the case, we
-  // need to force a layout to see |ui_element_container_|'s new contents.
-  if (child == ui_element_container_)
-    Layout();
+  SizeToContents();
 }
 
-void AssistantBubbleView::ChildVisibilityChanged(views::View* child) {
-  // When toggling the visibility of the caption bar or dialog plate, we also
-  // need to update the padding of the layout.
-  if (child == caption_bar_ || child == dialog_plate_) {
-    const int padding_top_dip = caption_bar_->visible() ? 0 : kPaddingDip;
-    const int padding_bottom_dip = dialog_plate_->visible() ? 0 : kPaddingDip;
-    layout_manager_->set_inside_border_insets(
-        gfx::Insets(padding_top_dip, 0, padding_bottom_dip, 0));
-  }
-  PreferredSizeChanged();
+int AssistantBubbleView::GetDialogButtons() const {
+  return ui::DIALOG_BUTTON_NONE;
 }
 
-void AssistantBubbleView::InitLayout() {
-  // Caption bar, dialog plate, suggestion container, and UI element container
-  // are not visible when using stylus modality.
-  const bool is_using_stylus =
-      assistant_controller_->interaction_model()->input_modality() ==
-      InputModality::kStylus;
-
-  // There should be no vertical padding on the layout when the caption bar
-  // and dialog plate are present.
-  const int padding_vertical_dip = is_using_stylus ? kPaddingDip : 0;
-
-  layout_manager_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical,
-      gfx::Insets(padding_vertical_dip, 0), kSpacingDip));
-
-  // Caption bar.
-  caption_bar_->SetVisible(!is_using_stylus);
-  AddChildView(caption_bar_);
-
-  // Interaction container.
-  AddChildView(interaction_container_);
-
-  // UI element container.
-  ui_element_container_->SetVisible(!is_using_stylus);
-  AddChildView(ui_element_container_);
-
-  layout_manager_->SetFlexForView(ui_element_container_, 1);
-
-  // Suggestions container.
-  suggestions_container_->SetVisible(false);
-  AddChildView(suggestions_container_);
-
-  // Dialog plate.
-  dialog_plate_->SetVisible(!is_using_stylus);
-  AddChildView(dialog_plate_);
+void AssistantBubbleView::OnBeforeBubbleWidgetInit(
+    views::Widget::InitParams* params,
+    views::Widget* widget) const {
+  params->corner_radius = kCornerRadiusDip;
+  params->keep_on_top = true;
+  params->shadow_type = views::Widget::InitParams::SHADOW_TYPE_DROP;
+  params->shadow_elevation = wm::kShadowElevationActiveWindow;
 }
 
-void AssistantBubbleView::OnInputModalityChanged(InputModality input_modality) {
-  // Caption bar, dialog plate, suggestion container, and UI element container
-  // are not visible when using stylus modality.
-  caption_bar_->SetVisible(input_modality != InputModality::kStylus);
-  dialog_plate_->SetVisible(input_modality != InputModality::kStylus);
-  suggestions_container_->SetVisible(input_modality != InputModality::kStylus);
-  ui_element_container_->SetVisible(input_modality != InputModality::kStylus);
+void AssistantBubbleView::Init() {
+  SetLayoutManager(std::make_unique<views::FillLayout>());
 
-  // If the query for the interaction is empty, we may need to update the prompt
-  // to reflect the current input modality.
-  if (assistant_controller_->interaction_model()->query().Empty()) {
-    interaction_container_->ClearQuery();
-  }
-}
-
-void AssistantBubbleView::OnInteractionStateChanged(
-    InteractionState interaction_state) {
-  if (interaction_state != InteractionState::kInactive)
-    return;
-
-  // When the Assistant UI is being hidden we need to reset our minimum height
-  // restriction so that the default restrictions are restored for the next
-  // time the view is shown.
-  min_height_dip_ = kMinHeightDip;
-  PreferredSizeChanged();
-}
-
-void AssistantBubbleView::OnQueryChanged(const AssistantQuery& query) {
-  interaction_container_->SetQuery(query);
-}
-
-void AssistantBubbleView::OnQueryCleared() {
-  interaction_container_->ClearQuery();
+  assistant_main_view_ = new AssistantMainView(assistant_controller_);
+  AddChildView(assistant_main_view_);
 }
 
 void AssistantBubbleView::RequestFocus() {
-  dialog_plate_->RequestFocus();
+  if (assistant_main_view_)
+    assistant_main_view_->RequestFocus();
+}
+
+void AssistantBubbleView::SetAnchor() {
+  // TODO(dmblack): Handle multiple displays, dynamic shelf repositioning, and
+  // any other corner cases.
+  // Anchors to bottom center of primary display's work area.
+  display::Display primary_display =
+      display::Screen::GetScreen()->GetPrimaryDisplay();
+
+  gfx::Rect work_area = primary_display.work_area();
+  gfx::Rect anchor = gfx::Rect(work_area.x(), work_area.bottom() - kMarginDip,
+                               work_area.width(), 0);
+
+  SetAnchorRect(anchor);
 }
 
 }  // namespace ash
diff --git a/ash/assistant/ui/assistant_bubble_view.h b/ash/assistant/ui/assistant_bubble_view.h
index 95d52c2a..7fbfdb2 100644
--- a/ash/assistant/ui/assistant_bubble_view.h
+++ b/ash/assistant/ui/assistant_bubble_view.h
@@ -5,58 +5,33 @@
 #ifndef ASH_ASSISTANT_UI_ASSISTANT_BUBBLE_VIEW_H_
 #define ASH_ASSISTANT_UI_ASSISTANT_BUBBLE_VIEW_H_
 
-#include "ash/assistant/model/assistant_interaction_model_observer.h"
 #include "base/macros.h"
-#include "ui/views/view.h"
-
-namespace views {
-class BoxLayout;
-}  // namespace views
+#include "ui/views/bubble/bubble_dialog_delegate.h"
 
 namespace ash {
 
 class AssistantController;
-class DialogPlate;
-class SuggestionContainerView;
-class UiElementContainerView;
+class AssistantMainView;
 
-namespace {
-class InteractionContainer;
-}  // namespace
-
-class AssistantBubbleView : public views::View,
-                            public AssistantInteractionModelObserver {
+class AssistantBubbleView : public views::BubbleDialogDelegateView {
  public:
   explicit AssistantBubbleView(AssistantController* assistant_controller);
   ~AssistantBubbleView() override;
 
-  // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  // views::BubbleDialogDelegateView:
   void ChildPreferredSizeChanged(views::View* child) override;
-  void ChildVisibilityChanged(views::View* child) override;
-  void OnBoundsChanged(const gfx::Rect& prev_bounds) override;
+  int GetDialogButtons() const override;
+  void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
+                                views::Widget* widget) const override;
+  void Init() override;
   void RequestFocus() override;
 
-  // AssistantInteractionModelObserver:
-  void OnInputModalityChanged(InputModality input_modality) override;
-  void OnInteractionStateChanged(InteractionState interaction_state) override;
-  void OnQueryChanged(const AssistantQuery& query) override;
-  void OnQueryCleared() override;
-
  private:
-  void InitLayout();
+  void SetAnchor();
 
   AssistantController* const assistant_controller_;  // Owned by Shell.
 
-  views::View* caption_bar_;                     // Owned by view hierarchy.
-  InteractionContainer* interaction_container_;  // Owned by view hierarchy.
-  UiElementContainerView* ui_element_container_;    // Owned by view hierarchy.
-  SuggestionContainerView* suggestions_container_;  // Owned by view hierarchy.
-  DialogPlate* dialog_plate_;                    // Owned by view hierarchy.
-
-  views::BoxLayout* layout_manager_ = nullptr;  // Owned by view hierarchy.
-
-  int min_height_dip_;
+  AssistantMainView* assistant_main_view_;  // Owned by view hierarchy.
 
   DISALLOW_COPY_AND_ASSIGN(AssistantBubbleView);
 };
diff --git a/ash/assistant/ui/assistant_main_view.cc b/ash/assistant/ui/assistant_main_view.cc
new file mode 100644
index 0000000..77c8bda95
--- /dev/null
+++ b/ash/assistant/ui/assistant_main_view.cc
@@ -0,0 +1,112 @@
+// 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.
+
+#include "ash/assistant/ui/assistant_main_view.h"
+
+#include <memory>
+
+#include "ash/assistant/assistant_controller.h"
+#include "ash/assistant/model/assistant_interaction_model.h"
+#include "ash/assistant/ui/assistant_ui_constants.h"
+#include "ash/assistant/ui/caption_bar.h"
+#include "ash/assistant/ui/dialog_plate/dialog_plate.h"
+#include "ash/assistant/ui/main_stage/ui_element_container_view.h"
+#include "ash/assistant/ui/suggestion_container_view.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+namespace {
+
+// Appearance.
+constexpr int kMinHeightDip = 200;
+constexpr int kMaxHeightDip = 640;
+
+}  // namespace
+
+AssistantMainView::AssistantMainView(AssistantController* assistant_controller)
+    : assistant_controller_(assistant_controller),
+      caption_bar_(new CaptionBar()),
+      ui_element_container_(new UiElementContainerView(assistant_controller)),
+      suggestions_container_(new SuggestionContainerView(assistant_controller)),
+      dialog_plate_(new DialogPlate(assistant_controller)),
+      min_height_dip_(kMinHeightDip) {
+  InitLayout();
+
+  // Observe changes to interaction model.
+  assistant_controller_->AddInteractionModelObserver(this);
+}
+
+AssistantMainView::~AssistantMainView() {
+  assistant_controller_->RemoveInteractionModelObserver(this);
+}
+
+gfx::Size AssistantMainView::CalculatePreferredSize() const {
+  // |min_height_dip_| <= |preferred_height| <= |kMaxHeightDip|.
+  int preferred_height = GetHeightForWidth(kPreferredWidthDip);
+  preferred_height = std::min(preferred_height, kMaxHeightDip);
+  preferred_height = std::max(preferred_height, min_height_dip_);
+  return gfx::Size(kPreferredWidthDip, preferred_height);
+}
+
+void AssistantMainView::OnBoundsChanged(const gfx::Rect& prev_bounds) {
+  // Until Assistant UI is hidden, the view may grow in height but not shrink.
+  min_height_dip_ = std::max(min_height_dip_, height());
+}
+
+void AssistantMainView::ChildPreferredSizeChanged(views::View* child) {
+  PreferredSizeChanged();
+
+  // We force a layout here because, though we are receiving a
+  // ChildPreferredSizeChanged event, it may be that the
+  // |ui_element_container_|'s bounds will not actually change due to the height
+  // restrictions imposed by AssistantMainView. When this is the case, we
+  // need to force a layout to see |ui_element_container_|'s new contents.
+  if (child == ui_element_container_)
+    Layout();
+}
+
+void AssistantMainView::ChildVisibilityChanged(views::View* child) {
+  PreferredSizeChanged();
+}
+
+void AssistantMainView::InitLayout() {
+  views::BoxLayout* layout_manager =
+      SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical, gfx::Insets(),
+          kSpacingDip));
+
+  // Caption bar.
+  AddChildView(caption_bar_);
+
+  // UI element container.
+  AddChildView(ui_element_container_);
+
+  layout_manager->SetFlexForView(ui_element_container_, 1);
+
+  // Suggestions container.
+  suggestions_container_->SetVisible(false);
+  AddChildView(suggestions_container_);
+
+  // Dialog plate.
+  AddChildView(dialog_plate_);
+}
+
+void AssistantMainView::OnInteractionStateChanged(
+    InteractionState interaction_state) {
+  if (interaction_state != InteractionState::kInactive)
+    return;
+
+  // When the Assistant UI is being hidden we need to reset our minimum height
+  // restriction so that the default restrictions are restored for the next
+  // time the view is shown.
+  min_height_dip_ = kMinHeightDip;
+  PreferredSizeChanged();
+}
+
+void AssistantMainView::RequestFocus() {
+  dialog_plate_->RequestFocus();
+}
+
+}  // namespace ash
diff --git a/ash/assistant/ui/assistant_main_view.h b/ash/assistant/ui/assistant_main_view.h
new file mode 100644
index 0000000..21ceebd
--- /dev/null
+++ b/ash/assistant/ui/assistant_main_view.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef ASH_ASSISTANT_UI_ASSISTANT_MAIN_VIEW_H_
+#define ASH_ASSISTANT_UI_ASSISTANT_MAIN_VIEW_H_
+
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "base/macros.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+class AssistantController;
+class DialogPlate;
+class SuggestionContainerView;
+class UiElementContainerView;
+
+class AssistantMainView : public views::View,
+                          public AssistantInteractionModelObserver {
+ public:
+  explicit AssistantMainView(AssistantController* assistant_controller);
+  ~AssistantMainView() override;
+
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+  void ChildPreferredSizeChanged(views::View* child) override;
+  void ChildVisibilityChanged(views::View* child) override;
+  void OnBoundsChanged(const gfx::Rect& prev_bounds) override;
+  void RequestFocus() override;
+
+  // AssistantInteractionModelObserver:
+  void OnInteractionStateChanged(InteractionState interaction_state) override;
+
+ private:
+  void InitLayout();
+
+  AssistantController* const assistant_controller_;  // Owned by Shell.
+
+  views::View* caption_bar_;                        // Owned by view hierarchy.
+  UiElementContainerView* ui_element_container_;    // Owned by view hierarchy.
+  SuggestionContainerView* suggestions_container_;  // Owned by view hierarchy.
+  DialogPlate* dialog_plate_;                       // Owned by view hierarchy.
+
+  int min_height_dip_;
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantMainView);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UI_ASSISTANT_MAIN_VIEW_H_
diff --git a/ash/assistant/ui/dialog_plate/dialog_plate.h b/ash/assistant/ui/dialog_plate/dialog_plate.h
index 10dfd90..08d675c6 100644
--- a/ash/assistant/ui/dialog_plate/dialog_plate.h
+++ b/ash/assistant/ui/dialog_plate/dialog_plate.h
@@ -15,7 +15,7 @@
 
 class AssistantController;
 
-// DialogPlate is the child of AssistantBubbleView concerned with providing the
+// DialogPlate is the child of AssistantMainView concerned with providing the
 // means by which a user converses with Assistant. To this end, DialogPlate
 // provides a textfield for use with the keyboard input modality, and an
 // ActionView which serves to either commit a text query, or toggle voice
diff --git a/ash/assistant/ui/main_stage/assistant_query_view.cc b/ash/assistant/ui/main_stage/assistant_query_view.cc
new file mode 100644
index 0000000..ddc0f14
--- /dev/null
+++ b/ash/assistant/ui/main_stage/assistant_query_view.cc
@@ -0,0 +1,153 @@
+// 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.
+
+#include "ash/assistant/ui/main_stage/assistant_query_view.h"
+
+#include <memory>
+
+#include "ash/assistant/assistant_controller.h"
+#include "ash/assistant/model/assistant_interaction_model.h"
+#include "ash/assistant/model/assistant_query.h"
+#include "ash/assistant/ui/assistant_ui_constants.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+namespace {
+
+// Appearance.
+constexpr int kIconSizeDip = 24;
+
+// TODO(b/77638210): Replace with localized resource strings.
+constexpr char kPrompt[] = "Hi, how can I help?";
+
+}  // namespace
+
+AssistantQueryView::AssistantQueryView(
+    AssistantController* assistant_controller)
+    : assistant_controller_(assistant_controller),
+      label_(new views::StyledLabel(base::string16(), /*listener=*/nullptr)) {
+  InitLayout();
+  OnQueryChanged(assistant_controller_->interaction_model()->query());
+
+  // The Assistant controller indirectly owns the view hierarchy to which
+  // AssistantQueryView belongs so is guaranteed to outlive it.
+  assistant_controller_->AddInteractionModelObserver(this);
+}
+
+AssistantQueryView::~AssistantQueryView() {
+  assistant_controller_->RemoveInteractionModelObserver(this);
+}
+
+gfx::Size AssistantQueryView::CalculatePreferredSize() const {
+  return gfx::Size(INT_MAX, GetHeightForWidth(INT_MAX));
+}
+
+void AssistantQueryView::ChildPreferredSizeChanged(views::View* child) {
+  PreferredSizeChanged();
+}
+
+void AssistantQueryView::OnBoundsChanged(const gfx::Rect& prev_bounds) {
+  label_->SizeToFit(width());
+}
+
+void AssistantQueryView::InitLayout() {
+  views::BoxLayout* layout_manager =
+      SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kVertical, gfx::Insets(),
+          kSpacingDip));
+
+  layout_manager->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_START);
+
+  // Icon.
+  views::ImageView* icon = new views::ImageView();
+  icon->SetImage(gfx::CreateVectorIcon(kAssistantIcon, kIconSizeDip));
+  icon->SetImageSize(gfx::Size(kIconSizeDip, kIconSizeDip));
+  icon->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
+  AddChildView(icon);
+
+  // Label.
+  label_->set_auto_color_readability_enabled(false);
+  label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  AddChildView(label_);
+}
+
+void AssistantQueryView::OnQueryChanged(const AssistantQuery& query) {
+  // Empty query.
+  if (query.Empty()) {
+    OnQueryCleared();
+  } else {
+    // Populated query.
+    switch (query.type()) {
+      case AssistantQueryType::kText: {
+        const AssistantTextQuery& text_query =
+            static_cast<const AssistantTextQuery&>(query);
+        SetText(text_query.text());
+        break;
+      }
+      case AssistantQueryType::kVoice: {
+        const AssistantVoiceQuery& voice_query =
+            static_cast<const AssistantVoiceQuery&>(query);
+        SetText(voice_query.high_confidence_speech(),
+                voice_query.low_confidence_speech());
+        break;
+      }
+      case AssistantQueryType::kEmpty:
+        // Empty queries are already handled.
+        NOTREACHED();
+        break;
+    }
+  }
+}
+
+void AssistantQueryView::OnQueryCleared() {
+  SetText(kPrompt);
+}
+
+void AssistantQueryView::SetText(const std::string& high_confidence_text,
+                                 const std::string& low_confidence_text) {
+  const base::string16& high_confidence_text_16 =
+      base::UTF8ToUTF16(high_confidence_text);
+
+  if (low_confidence_text.empty()) {
+    label_->SetText(high_confidence_text_16);
+    label_->AddStyleRange(gfx::Range(0, high_confidence_text_16.length()),
+                          CreateStyleInfo(kTextColorPrimary));
+  } else {
+    const base::string16& low_confidence_text_16 =
+        base::UTF8ToUTF16(low_confidence_text);
+
+    label_->SetText(high_confidence_text_16 + low_confidence_text_16);
+
+    // High confidence text styling.
+    label_->AddStyleRange(gfx::Range(0, high_confidence_text_16.length()),
+                          CreateStyleInfo(kTextColorPrimary));
+
+    // Low confidence text styling.
+    label_->AddStyleRange(gfx::Range(high_confidence_text_16.length(),
+                                     high_confidence_text_16.length() +
+                                         low_confidence_text_16.length()),
+                          CreateStyleInfo(kTextColorHint));
+  }
+
+  label_->SizeToFit(width());
+  PreferredSizeChanged();
+}
+
+views::StyledLabel::RangeStyleInfo AssistantQueryView::CreateStyleInfo(
+    SkColor color) const {
+  views::StyledLabel::RangeStyleInfo style;
+  style.custom_font =
+      label_->GetDefaultFontList().DeriveWithSizeDelta(8).DeriveWithWeight(
+          gfx::Font::Weight::MEDIUM);
+  style.override_color = color;
+  return style;
+}
+
+}  // namespace ash
diff --git a/ash/assistant/ui/main_stage/assistant_query_view.h b/ash/assistant/ui/main_stage/assistant_query_view.h
new file mode 100644
index 0000000..5dcfa5d
--- /dev/null
+++ b/ash/assistant/ui/main_stage/assistant_query_view.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef ASH_ASSISTANT_UI_MAIN_STAGE_ASSISTANT_QUERY_VIEW_H_
+#define ASH_ASSISTANT_UI_MAIN_STAGE_ASSISTANT_QUERY_VIEW_H_
+
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "base/macros.h"
+#include "ui/views/controls/styled_label.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+class AssistantController;
+
+// AssistantQueryView is the visual representation of an AssistantQuery. It is a
+// child view of UiElementContainerView.
+class AssistantQueryView : public views::View,
+                           public AssistantInteractionModelObserver {
+ public:
+  explicit AssistantQueryView(AssistantController* assistant_controller);
+  ~AssistantQueryView() override;
+
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+  void ChildPreferredSizeChanged(views::View* child) override;
+  void OnBoundsChanged(const gfx::Rect& prev_bounds) override;
+
+  // AssistantInteractionModelObserver:
+  void OnQueryChanged(const AssistantQuery& query) override;
+  void OnQueryCleared() override;
+
+ private:
+  void InitLayout();
+  void SetText(const std::string& high_confidence_text,
+               const std::string& low_confidence_text = std::string());
+  views::StyledLabel::RangeStyleInfo CreateStyleInfo(SkColor color) const;
+
+  AssistantController* const assistant_controller_;  // Owned by Shell.
+  views::StyledLabel* label_;                        // Owned by view hierarchy.
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantQueryView);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UI_MAIN_STAGE_ASSISTANT_QUERY_VIEW_H_
diff --git a/ash/assistant/ui/main_stage/ui_element_container_view.cc b/ash/assistant/ui/main_stage/ui_element_container_view.cc
index 0df384c..e8a6003 100644
--- a/ash/assistant/ui/main_stage/ui_element_container_view.cc
+++ b/ash/assistant/ui/main_stage/ui_element_container_view.cc
@@ -7,6 +7,7 @@
 #include "ash/assistant/assistant_controller.h"
 #include "ash/assistant/model/assistant_ui_element.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
+#include "ash/assistant/ui/main_stage/assistant_query_view.h"
 #include "ash/assistant/ui/main_stage/assistant_text_element_view.h"
 #include "ash/public/cpp/app_list/answer_card_contents_registry.h"
 #include "base/callback.h"
@@ -18,6 +19,8 @@
 UiElementContainerView::UiElementContainerView(
     AssistantController* assistant_controller)
     : assistant_controller_(assistant_controller),
+      assistant_query_view_(
+          std::make_unique<AssistantQueryView>(assistant_controller)),
       render_request_weak_factory_(this) {
   InitLayout();
 
@@ -43,6 +46,10 @@
 
   layout_manager->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_START);
+
+  // Query.
+  assistant_query_view_->set_owned_by_client();
+  AddChildView(assistant_query_view_.get());
 }
 
 void UiElementContainerView::OnUiElementAdded(
@@ -69,6 +76,8 @@
   render_request_weak_factory_.InvalidateWeakPtrs();
 
   RemoveAllChildViews(/*delete_children=*/true);
+  AddChildView(assistant_query_view_.get());
+
   PreferredSizeChanged();
 
   ReleaseAllCards();
@@ -115,6 +124,7 @@
   // When the card has been rendered in the same process, its view is
   // available in the AnswerCardContentsRegistry's token-to-view map.
   if (app_list::AnswerCardContentsRegistry::Get()) {
+    RemoveChildView(assistant_query_view_.get());
     AddChildView(
         app_list::AnswerCardContentsRegistry::Get()->GetView(embed_token));
   }
@@ -131,7 +141,9 @@
     const AssistantTextElement* text_element) {
   DCHECK(!is_processing_ui_element_);
 
+  RemoveChildView(assistant_query_view_.get());
   AddChildView(new AssistantTextElementView(text_element));
+
   PreferredSizeChanged();
 }
 
diff --git a/ash/assistant/ui/main_stage/ui_element_container_view.h b/ash/assistant/ui/main_stage/ui_element_container_view.h
index f5f0c46..75f887f 100644
--- a/ash/assistant/ui/main_stage/ui_element_container_view.h
+++ b/ash/assistant/ui/main_stage/ui_element_container_view.h
@@ -17,10 +17,11 @@
 
 class AssistantCardElement;
 class AssistantController;
+class AssistantQueryView;
 class AssistantTextElement;
 class AssistantUiElement;
 
-// UiElementContainerView is the child of AssistantBubbleView concerned with
+// UiElementContainerView is the child of AssistantMainView concerned with
 // laying out text views and embedded card views in response to Assistant
 // interaction model UI element events.
 class UiElementContainerView : public views::View,
@@ -54,6 +55,7 @@
   void ReleaseAllCards();
 
   AssistantController* const assistant_controller_;  // Owned by Shell.
+  std::unique_ptr<AssistantQueryView> assistant_query_view_;
 
   // Uniquely identifies cards owned by AssistantCardRenderer.
   std::vector<base::UnguessableToken> id_token_list_;
diff --git a/ash/assistant/ui/suggestion_container_view.h b/ash/assistant/ui/suggestion_container_view.h
index 56f5c69..4196a2d 100644
--- a/ash/assistant/ui/suggestion_container_view.h
+++ b/ash/assistant/ui/suggestion_container_view.h
@@ -16,7 +16,7 @@
 
 class AssistantController;
 
-// SuggestionContainerView is the child of AssistantBubbleView concerned with
+// SuggestionContainerView is the child of AssistantMainView concerned with
 // laying out SuggestionChipViews in response to Assistant interaction model
 // suggestion events.
 class SuggestionContainerView : public views::ScrollView,
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index f44d5294..d293484 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -780,7 +780,17 @@
     const keyboard::KeyboardControllerState state) {
   if (state == keyboard::KeyboardControllerState::SHOWN ||
       state == keyboard::KeyboardControllerState::HIDDEN) {
-    LayoutAuth(primary_big_view_, opt_secondary_big_view_, false /*animate*/);
+    bool keyboard_will_be_shown =
+        state == keyboard::KeyboardControllerState::SHOWN;
+    // Keyboard state can go from SHOWN -> SomeStateOtherThanShownOrHidden ->
+    // SHOWN when we click on the inactive BigUser while the virtual keyboard is
+    // active. In this case, we should do nothing, since
+    // SwapActiveAuthBetweenPrimaryAndSecondary handles the re-layout.
+    if (keyboard_shown_ == keyboard_will_be_shown)
+      return;
+    keyboard_shown_ = keyboard_will_be_shown;
+    LayoutAuth(CurrentBigUserView(), nullptr /*opt_to_hide*/,
+               false /*animate*/);
   }
 }
 
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 84384458..da0773d6 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -25,6 +25,7 @@
 #include "chromeos/dbus/power_manager_client.h"
 #include "ui/display/display_observer.h"
 #include "ui/display/screen.h"
+#include "ui/keyboard/keyboard_controller.h"
 #include "ui/keyboard/keyboard_controller_observer.h"
 #include "ui/views/controls/styled_label_listener.h"
 #include "ui/views/view.h"
@@ -385,6 +386,11 @@
   // Expanded view for public account user to select language and keyboard.
   LoginExpandedPublicAccountView* expanded_view_ = nullptr;
 
+  // Whether the virtual keyboard is currently shown. Only changes when the
+  // keyboard state changes to KeyboardControllerState::SHOWN or to
+  // KeyboardControllerState::HIDDEN.
+  bool keyboard_shown_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(LockContentsView);
 };
 
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index ae7c313..4c0d1a2 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -1021,6 +1021,53 @@
   EXPECT_TRUE(pin_view->visible());
 }
 
+// Verifies that swapping auth users while the virtual keyboard is active
+// focuses the other user's password field.
+TEST_F(LockContentsViewKeyboardUnitTest, SwitchUserWhileKeyboardShown) {
+  ASSERT_NO_FATAL_FAILURE(ShowLoginScreen());
+  LockContentsView* contents =
+      LockScreen::TestApi(LockScreen::Get()).contents_view();
+  ASSERT_NE(nullptr, contents);
+
+  LoadUsers(2);
+
+  LoginAuthUserView::TestApi primary_user(
+      LockContentsView::TestApi(contents).primary_big_view()->auth_user());
+  LoginAuthUserView::TestApi secondary_user(LockContentsView::TestApi(contents)
+                                                .opt_secondary_big_view()
+                                                ->auth_user());
+
+  ASSERT_NO_FATAL_FAILURE(ShowKeyboard());
+  EXPECT_TRUE(LoginPasswordView::TestApi(primary_user.password_view())
+                  .textfield()
+                  ->HasFocus());
+
+  // Simulate a button click on the secondary UserView.
+  ui::test::EventGenerator& generator = GetEventGenerator();
+  generator.MoveMouseTo(
+      secondary_user.user_view()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+
+  EXPECT_TRUE(LoginPasswordView::TestApi(secondary_user.password_view())
+                  .textfield()
+                  ->HasFocus());
+  EXPECT_FALSE(LoginPasswordView::TestApi(primary_user.password_view())
+                   .textfield()
+                   ->HasFocus());
+
+  // Simulate a button click on the primary UserView.
+  generator.MoveMouseTo(
+      primary_user.user_view()->GetBoundsInScreen().CenterPoint());
+  generator.ClickLeftButton();
+
+  EXPECT_TRUE(LoginPasswordView::TestApi(primary_user.password_view())
+                  .textfield()
+                  ->HasFocus());
+  EXPECT_FALSE(LoginPasswordView::TestApi(secondary_user.password_view())
+                   .textfield()
+                   ->HasFocus());
+}
+
 // Verify that swapping works in two user layout between one regular auth user
 // and one public account user.
 TEST_F(LockContentsViewUnitTest, SwapAuthAndPublicAccountUserInTwoUserLayout) {
diff --git a/base/files/file_posix.cc b/base/files/file_posix.cc
index d538b66..45cef58 100644
--- a/base/files/file_posix.cc
+++ b/base/files/file_posix.cc
@@ -275,8 +275,17 @@
   int bytes_written = 0;
   int rv;
   do {
+#if defined(OS_ANDROID)
+    // In case __USE_FILE_OFFSET64 is not used, we need to call pwrite64()
+    // instead of pwrite().
+    static_assert(sizeof(int64_t) == sizeof(off64_t),
+                  "off64_t must be 64 bits");
+    rv = HANDLE_EINTR(pwrite64(file_.get(), data + bytes_written,
+                               size - bytes_written, offset + bytes_written));
+#else
     rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written,
                              size - bytes_written, offset + bytes_written));
+#endif
     if (rv <= 0)
       break;
 
diff --git a/base/files/file_unittest.cc b/base/files/file_unittest.cc
index 112b90d5..29bee00 100644
--- a/base/files/file_unittest.cc
+++ b/base/files/file_unittest.cc
@@ -540,6 +540,27 @@
   ASSERT_FALSE(base::PathExists(file_path));
 }
 
+TEST(FileTest, WriteDataToLargeOffset) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath file_path = temp_dir.GetPath().AppendASCII("file");
+  File file(file_path,
+            (base::File::FLAG_CREATE | base::File::FLAG_READ |
+             base::File::FLAG_WRITE | base::File::FLAG_DELETE_ON_CLOSE));
+  ASSERT_TRUE(file.IsValid());
+
+  const char kData[] = "this file is sparse.";
+  const int kDataLen = sizeof(kData) - 1;
+  const int64_t kLargeFileOffset = (1LL << 31);
+
+  // If the file fails to write, it is probably we are running out of disk space
+  // and the file system doesn't support sparse file.
+  if (file.Write(kLargeFileOffset - kDataLen - 1, kData, kDataLen) < 0)
+    return;
+
+  ASSERT_EQ(kDataLen, file.Write(kLargeFileOffset + 1, kData, kDataLen));
+}
+
 #if defined(OS_WIN)
 TEST(FileTest, GetInfoForDirectory) {
   base::ScopedTempDir temp_dir;
diff --git a/base/profiler/stack_sampling_profiler.cc b/base/profiler/stack_sampling_profiler.cc
index 4c76434e..879bf74b 100644
--- a/base/profiler/stack_sampling_profiler.cc
+++ b/base/profiler/stack_sampling_profiler.cc
@@ -145,13 +145,12 @@
   };
 
   struct CollectionContext {
-    CollectionContext(int profiler_id,
-                      PlatformThreadId target,
+    CollectionContext(PlatformThreadId target,
                       const SamplingParams& params,
                       const CompletedCallback& callback,
                       WaitableEvent* finished,
                       std::unique_ptr<NativeStackSampler> sampler)
-        : profiler_id(profiler_id),
+        : collection_id(next_collection_id.GetNext()),
           target(target),
           params(params),
           callback(callback),
@@ -159,9 +158,9 @@
           native_sampler(std::move(sampler)) {}
     ~CollectionContext() = default;
 
-    // An identifier for the profiler associated with this collection, used to
-    // uniquely identify the collection to outside interests.
-    const int profiler_id;
+    // An identifier for this collection, used to uniquely identify the
+    // collection to outside interests.
+    const int collection_id;
 
     const PlatformThreadId target;     // ID of The thread being sampled.
     const SamplingParams params;       // Information about how to sample.
@@ -184,22 +183,22 @@
     // The collected stack samples. The active profile is always at the back().
     CallStackProfiles profiles;
 
-    // Sequence number for generating new profiler ids.
-    static AtomicSequenceNumber next_profiler_id;
+    // Sequence number for generating new collection ids.
+    static AtomicSequenceNumber next_collection_id;
   };
 
   // Gets the single instance of this class.
   static SamplingThread* GetInstance();
 
   // Adds a new CollectionContext to the thread. This can be called externally
-  // from any thread. This returns an ID that can later be used to stop
-  // the sampling.
+  // from any thread. This returns a collection id that can later be used to
+  // stop the sampling.
   int Add(std::unique_ptr<CollectionContext> collection);
 
-  // Removes an active collection based on its ID, forcing it to run its
-  // callback if any data has been collected. This can be called externally
+  // Removes an active collection based on its collection id, forcing it to run
+  // its callback if any data has been collected. This can be called externally
   // from any thread.
-  void Remove(int id);
+  void Remove(int collection_id);
 
  private:
   friend class TestAPI;
@@ -249,8 +248,8 @@
 
   // These methods are tasks that get posted to the internal message queue.
   void AddCollectionTask(std::unique_ptr<CollectionContext> collection);
-  void RemoveCollectionTask(int id);
-  void PerformCollectionTask(int id);
+  void RemoveCollectionTask(int collection_id);
+  void PerformCollectionTask(int collection_id);
   void ShutdownTask(int add_events);
 
   // Updates the |next_sample_time| time based on configured parameters.
@@ -265,10 +264,10 @@
   // that take it are not called concurrently.
   std::unique_ptr<NativeStackSampler::StackBuffer> stack_buffer_;
 
-  // A map of IDs to collection contexts. Because this class is a singleton
-  // that is never destroyed, context objects will never be destructed except
-  // by explicit action. Thus, it's acceptable to pass unretained pointers
-  // to these objects when posting tasks.
+  // A map of collection ids to collection contexts. Because this class is a
+  // singleton that is never destroyed, context objects will never be destructed
+  // except by explicit action. Thus, it's acceptable to pass unretained
+  // pointers to these objects when posting tasks.
   std::map<int, std::unique_ptr<CollectionContext>> active_collections_;
 
   // State maintained about the current execution (or non-execution) of
@@ -371,8 +370,8 @@
   event->Signal();
 }
 
-AtomicSequenceNumber
-    StackSamplingProfiler::SamplingThread::CollectionContext::next_profiler_id;
+AtomicSequenceNumber StackSamplingProfiler::SamplingThread::CollectionContext::
+    next_collection_id;
 
 StackSamplingProfiler::SamplingThread::SamplingThread()
     : Thread("StackSamplingProfiler") {}
@@ -388,7 +387,7 @@
     std::unique_ptr<CollectionContext> collection) {
   // This is not to be run on the sampling thread.
 
-  int id = collection->profiler_id;
+  int collection_id = collection->collection_id;
   scoped_refptr<SingleThreadTaskRunner> task_runner =
       GetOrCreateTaskRunnerForAdd();
 
@@ -396,10 +395,10 @@
       FROM_HERE, BindOnce(&SamplingThread::AddCollectionTask, Unretained(this),
                           std::move(collection)));
 
-  return id;
+  return collection_id;
 }
 
-void StackSamplingProfiler::SamplingThread::Remove(int id) {
+void StackSamplingProfiler::SamplingThread::Remove(int collection_id) {
   // This is not to be run on the sampling thread.
 
   ThreadExecutionState state;
@@ -411,9 +410,9 @@
   // This can fail if the thread were to exit between acquisition of the task
   // runner above and the call below. In that case, however, everything has
   // stopped so there's no need to try to stop it.
-  task_runner->PostTask(
-      FROM_HERE,
-      BindOnce(&SamplingThread::RemoveCollectionTask, Unretained(this), id));
+  task_runner->PostTask(FROM_HERE,
+                        BindOnce(&SamplingThread::RemoveCollectionTask,
+                                 Unretained(this), collection_id));
 }
 
 scoped_refptr<SingleThreadTaskRunner>
@@ -496,7 +495,7 @@
 void StackSamplingProfiler::SamplingThread::FinishCollection(
     CollectionContext* collection) {
   DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
-  DCHECK_EQ(0u, active_collections_.count(collection->profiler_id));
+  DCHECK_EQ(0u, active_collections_.count(collection->collection_id));
 
   // If there is no duration for the final profile (because it was stopped),
   // calculate it now.
@@ -577,16 +576,16 @@
     std::unique_ptr<CollectionContext> collection) {
   DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
 
-  const int profiler_id = collection->profiler_id;
+  const int collection_id = collection->collection_id;
   const TimeDelta initial_delay = collection->params.initial_delay;
 
   active_collections_.insert(
-      std::make_pair(profiler_id, std::move(collection)));
+      std::make_pair(collection_id, std::move(collection)));
 
   GetTaskRunnerOnSamplingThread()->PostDelayedTask(
       FROM_HERE,
       BindOnce(&SamplingThread::PerformCollectionTask, Unretained(this),
-               profiler_id),
+               collection_id),
       initial_delay);
 
   // Another increment of "add events" serves to invalidate any pending
@@ -598,26 +597,28 @@
   }
 }
 
-void StackSamplingProfiler::SamplingThread::RemoveCollectionTask(int id) {
+void StackSamplingProfiler::SamplingThread::RemoveCollectionTask(
+    int collection_id) {
   DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
 
-  auto found = active_collections_.find(id);
+  auto found = active_collections_.find(collection_id);
   if (found == active_collections_.end())
     return;
 
   // Remove |collection| from |active_collections_|.
   std::unique_ptr<CollectionContext> collection = std::move(found->second);
-  size_t count = active_collections_.erase(id);
+  size_t count = active_collections_.erase(collection_id);
   DCHECK_EQ(1U, count);
 
   FinishCollection(collection.get());
   ScheduleShutdownIfIdle();
 }
 
-void StackSamplingProfiler::SamplingThread::PerformCollectionTask(int id) {
+void StackSamplingProfiler::SamplingThread::PerformCollectionTask(
+    int collection_id) {
   DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId());
 
-  auto found = active_collections_.find(id);
+  auto found = active_collections_.find(collection_id);
 
   // The task won't be found if it has been stopped.
   if (found == active_collections_.end())
@@ -637,7 +638,8 @@
   if (!collection_finished) {
     bool success = GetTaskRunnerOnSamplingThread()->PostDelayedTask(
         FROM_HERE,
-        BindOnce(&SamplingThread::PerformCollectionTask, Unretained(this), id),
+        BindOnce(&SamplingThread::PerformCollectionTask, Unretained(this),
+                 collection_id),
         std::max(collection->next_sample_time - Time::Now(), TimeDelta()));
     DCHECK(success);
     return;
@@ -647,7 +649,7 @@
   // to be restarted, a new collection task will be added below.
   std::unique_ptr<CollectionContext> owned_collection =
       std::move(found->second);
-  size_t count = active_collections_.erase(id);
+  size_t count = active_collections_.erase(collection_id);
   DCHECK_EQ(1U, count);
 
   // All capturing has completed so finish the collection.
@@ -819,7 +821,6 @@
   DCHECK_EQ(NULL_PROFILER_ID, profiler_id_);
   profiler_id_ = SamplingThread::GetInstance()->Add(
       std::make_unique<SamplingThread::CollectionContext>(
-          SamplingThread::CollectionContext::next_profiler_id.GetNext(),
           thread_id_, params_, completed_callback_, &profiling_inactive_,
           std::move(native_sampler)));
   DCHECK_NE(NULL_PROFILER_ID, profiler_id_);
@@ -866,10 +867,8 @@
 
 bool operator<(const StackSamplingProfiler::Sample& a,
                const StackSamplingProfiler::Sample& b) {
-  if (a.process_milestones < b.process_milestones)
-    return true;
-  if (a.process_milestones > b.process_milestones)
-    return false;
+  if (a.process_milestones != b.process_milestones)
+    return a.process_milestones < b.process_milestones;
 
   return a.frames < b.frames;
 }
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index ca52fb3..21e9fe85 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -51,11 +51,13 @@
 # The amount of time a test executable may run before it gets killed.
 _TEST_TIMEOUT_SECONDS = 30*60
 
+# Tests that use SpawnedTestServer must run the LocalTestServerSpawner on the
+# host machine.
 # TODO(jbudorick): Move this up to the test instance if the net test server is
 # handled outside of the APK for the remote_device environment.
 _SUITE_REQUIRES_TEST_SERVER_SPAWNER = [
   'components_browsertests', 'content_unittests', 'content_browsertests',
-  'net_unittests', 'unit_tests'
+  'net_unittests', 'services_unittests', 'unit_tests'
 ]
 
 # No-op context manager. If we used Python 3, we could change this to
diff --git a/build/config/mac/base_rules.gni b/build/config/mac/base_rules.gni
index 6934833..bcb34a16 100644
--- a/build/config/mac/base_rules.gni
+++ b/build/config/mac/base_rules.gni
@@ -243,7 +243,10 @@
       "XCODE_VERSION=$xcode_version",
     ]
     if (is_mac) {
-      substitutions += [ "MACOSX_DEPLOYMENT_TARGET=$mac_deployment_target" ]
+      substitutions += [
+        "MACOSX_DEPLOYMENT_TARGET=$mac_deployment_target",
+        "CHROMIUM_MIN_SYSTEM_VERSION=$mac_min_system_version",
+      ]
     } else if (is_ios) {
       substitutions += [ "IOS_DEPLOYMENT_TARGET=$ios_deployment_target" ]
     }
diff --git a/build/config/mac/mac_sdk.gni b/build/config/mac/mac_sdk.gni
index 5aa4ef17..544c524 100644
--- a/build/config/mac/mac_sdk.gni
+++ b/build/config/mac/mac_sdk.gni
@@ -7,10 +7,23 @@
 import("//build/toolchain/toolchain.gni")
 
 declare_args() {
-  # Minimum supported version of macOS. Must be of the form x.x.x for
-  # Info.plist files.
+  # The MACOSX_DEPLOYMENT_TARGET variable used when compiling. This partially
+  # controls the minimum supported version of macOS for Chromium by
+  # affecting the symbol availability rules. This may differ from
+  # mac_min_system_version when dropping support for older macOSes but where
+  # additional code changes are required to be compliant with the availability
+  # rules.
+  # Must be of the form x.x.x for Info.plist files.
   mac_deployment_target = "10.9.0"
 
+  # The value of the LSMinimmumSystemVersion in Info.plist files. This partially
+  # controls the minimum supported version of macOS for Chromium by
+  # affecting the Info.plist. This may differ from mac_deployment_target when
+  # dropping support for older macOSes. This should be greater than or equal to
+  # the mac_deployment_target version.
+  # Must be of the form x.x.x for Info.plist files.
+  mac_min_system_version = "10.10.0"
+
   # Path to a specific version of the Mac SDK, not including a slash at the end.
   # If empty, the path to the lowest version greater than or equal to
   # mac_sdk_min is used.
diff --git a/build/fuchsia/sdk.sha1 b/build/fuchsia/sdk.sha1
index edaf712c..0cd2459 100644
--- a/build/fuchsia/sdk.sha1
+++ b/build/fuchsia/sdk.sha1
@@ -1 +1 @@
-da9c6cf50659326df8c39d1f53ab7d2b1eab2df6
\ No newline at end of file
+0aeaf51bb997641043c21b02d032d9538a906bca
\ No newline at end of file
diff --git a/cc/test/fake_layer_tree_host_impl_client.h b/cc/test/fake_layer_tree_host_impl_client.h
index 1556e94..a245d48 100644
--- a/cc/test/fake_layer_tree_host_impl_client.h
+++ b/cc/test/fake_layer_tree_host_impl_client.h
@@ -39,7 +39,8 @@
   void RequestBeginMainFrameNotExpected(bool new_state) override {}
   void NotifyImageDecodeRequestFinished() override {}
   void DidPresentCompositorFrameOnImplThread(
-      const std::vector<int>& source_frames,
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
       base::TimeTicks time,
       base::TimeDelta refresh,
       uint32_t flags) override {}
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index b471822..3ce0199f 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -299,21 +299,13 @@
   }
 
   sync_tree->set_source_frame_number(SourceFrameNumber());
-
-  // Set presentation token if any pending .
-  bool request_presentation_time = settings_.always_request_presentation_time;
-  if (!pending_presentation_time_callbacks_.empty()) {
-    request_presentation_time = true;
-    frame_to_presentation_time_callbacks_[SourceFrameNumber()] =
-        std::move(pending_presentation_time_callbacks_);
-    pending_presentation_time_callbacks_.clear();
-  } else if (!frame_to_presentation_time_callbacks_.empty()) {
-    // There are pending callbacks. Keep requesting the presentation callback
-    // in case a previous frame was dropped (the callbacks run when the frame
-    // makes it to screen).
-    request_presentation_time = true;
-  }
-  sync_tree->set_request_presentation_time(request_presentation_time);
+  bool request_presentation_time =
+      settings_.always_request_presentation_time ||
+      !pending_presentation_time_callbacks_.empty();
+  if (request_presentation_time && pending_presentation_time_callbacks_.empty())
+    pending_presentation_time_callbacks_.push_back(base::DoNothing());
+  sync_tree->AddPresentationCallbacks(
+      std::move(pending_presentation_time_callbacks_));
 
   if (needs_full_tree_sync_)
     TreeSynchronizer::SynchronizeTrees(root_layer(), sync_tree);
@@ -689,19 +681,13 @@
 }
 
 void LayerTreeHost::DidPresentCompositorFrame(
-    const std::vector<int>& source_frames,
+    uint32_t frame_token,
+    std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
     base::TimeTicks time,
     base::TimeDelta refresh,
     uint32_t flags) {
-  for (int frame : source_frames) {
-    if (!frame_to_presentation_time_callbacks_.count(frame))
-      continue;
-
-    for (auto& callback : frame_to_presentation_time_callbacks_[frame])
-      std::move(callback).Run(time, refresh, flags);
-
-    frame_to_presentation_time_callbacks_.erase(frame);
-  }
+  for (auto& callback : callbacks)
+    std::move(callback).Run(time, refresh, flags);
 }
 
 void LayerTreeHost::DidCompletePageScaleAnimation() {
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 2fbf3e6..c12c5bc 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -454,10 +454,12 @@
     client_->DidReceiveCompositorFrameAck();
   }
   bool UpdateLayers();
-  void DidPresentCompositorFrame(const std::vector<int>& source_frames,
-                                 base::TimeTicks time,
-                                 base::TimeDelta refresh,
-                                 uint32_t flags);
+  void DidPresentCompositorFrame(
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
+      base::TimeTicks time,
+      base::TimeDelta refresh,
+      uint32_t flags);
   // Called when the compositor completed page scale animation.
   void DidCompletePageScaleAnimation();
   void ApplyScrollAndScale(ScrollAndScaleSet* info);
@@ -708,11 +710,6 @@
   // added here.
   std::vector<PresentationTimeCallback> pending_presentation_time_callbacks_;
 
-  // Maps from the source frame presentation callbacks are requested for to
-  // the callbacks.
-  std::map<int, std::vector<PresentationTimeCallback>>
-      frame_to_presentation_time_callbacks_;
-
   DISALLOW_COPY_AND_ASSIGN(LayerTreeHost);
 };
 
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 1f9871e..c9185237 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -134,6 +134,14 @@
   return has_fixed_page_scale || has_mobile_viewport;
 }
 
+// Compares two presentation tokens, handling cases where the token
+// wraps around the 32-bit max value.
+bool PresentationTokenGT(uint32_t token1, uint32_t token2) {
+  // There will be underflow in the subtraction if token1 was created
+  // after token2.
+  return (token2 - token1) > 0x80000000u;
+}
+
 viz::ResourceFormat TileRasterBufferFormat(
     const LayerTreeSettings& settings,
     viz::ContextProvider* context_provider,
@@ -1728,26 +1736,27 @@
   client_->DidReceiveCompositorFrameAckOnImplThread();
 }
 
-void LayerTreeHostImpl::DidPresentCompositorFrame(uint32_t presentation_token,
+void LayerTreeHostImpl::DidPresentCompositorFrame(uint32_t frame_token,
                                                   base::TimeTicks time,
                                                   base::TimeDelta refresh,
                                                   uint32_t flags) {
   TRACE_EVENT_MARK_WITH_TIMESTAMP0("cc,benchmark", "FramePresented", time);
-  std::vector<int> source_frames;
-  auto iter = presentation_token_to_frame_.begin();
-  for (; iter != presentation_token_to_frame_.end() &&
-         iter->first <= presentation_token;
-       ++iter) {
-    source_frames.push_back(iter->second);
+  std::vector<LayerTreeHost::PresentationTimeCallback> all_callbacks;
+  while (!presentation_callbacks_.empty()) {
+    auto iter = presentation_callbacks_.begin();
+    if (PresentationTokenGT(iter->first, frame_token))
+      break;
+    auto& callbacks = iter->second;
+    std::copy(std::make_move_iterator(callbacks.begin()),
+              std::make_move_iterator(callbacks.end()),
+              std::back_inserter(all_callbacks));
+    presentation_callbacks_.erase(iter);
   }
-  presentation_token_to_frame_.erase(presentation_token_to_frame_.begin(),
-                                     iter);
-  client_->DidPresentCompositorFrameOnImplThread(source_frames, time, refresh,
-                                                 flags);
+  client_->DidPresentCompositorFrameOnImplThread(
+      frame_token, std::move(all_callbacks), time, refresh, flags);
 }
 
-void LayerTreeHostImpl::DidDiscardCompositorFrame(uint32_t presentation_token) {
-}
+void LayerTreeHostImpl::DidDiscardCompositorFrame(uint32_t frame_token) {}
 
 void LayerTreeHostImpl::ReclaimResources(
     const std::vector<viz::ReturnedResource>& resources) {
@@ -1833,11 +1842,8 @@
   metadata.frame_token = next_frame_token_++;
   if (!next_frame_token_)
     next_frame_token_ = 1u;
-  if (active_tree_->request_presentation_time()) {
+  if (active_tree_->has_presentation_callbacks())
     metadata.request_presentation_feedback = true;
-    presentation_token_to_frame_[metadata.frame_token] =
-        active_tree_->source_frame_number();
-  }
 
   metadata.device_scale_factor = active_tree_->painted_device_scale_factor() *
                                  active_tree_->device_scale_factor();
@@ -1859,6 +1865,11 @@
   metadata.content_source_id = active_tree_->content_source_id();
 
   active_tree_->GetViewportSelection(&metadata.selection);
+  if (active_tree_->has_presentation_callbacks()) {
+    presentation_callbacks_.push_back(
+        {metadata.frame_token, active_tree_->TakePresentationCallbacks()});
+    DCHECK_LE(presentation_callbacks_.size(), 25u);
+  }
 
   if (const auto* outer_viewport_scroll_node = OuterViewportScrollNode()) {
     metadata.root_overflow_y_hidden =
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 68841c26..b279fc9 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/containers/circular_deque.h"
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/memory/memory_pressure_listener.h"
@@ -36,6 +37,7 @@
 #include "cc/tiles/image_decode_cache.h"
 #include "cc/tiles/tile_manager.h"
 #include "cc/trees/layer_tree_frame_sink_client.h"
+#include "cc/trees/layer_tree_host.h"
 #include "cc/trees/layer_tree_mutator.h"
 #include "cc/trees/layer_tree_settings.h"
 #include "cc/trees/managed_memory_policy.h"
@@ -143,10 +145,11 @@
 
   virtual void RequestBeginMainFrameNotExpected(bool new_state) = 0;
 
-  // Called when a presentation time is requested. |source_frames| identifies
-  // the frames that correspond to the request.
+  // Called when a presentation time is requested. |frame_token| identifies
+  // the frame that was presented.
   virtual void DidPresentCompositorFrameOnImplThread(
-      const std::vector<int>& source_frames,
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
       base::TimeTicks time,
       base::TimeDelta refresh,
       uint32_t flags) = 0;
@@ -429,11 +432,11 @@
   base::Optional<viz::HitTestRegionList> BuildHitTestData() override;
   void DidLoseLayerTreeFrameSink() override;
   void DidReceiveCompositorFrameAck() override;
-  void DidPresentCompositorFrame(uint32_t presentation_token,
+  void DidPresentCompositorFrame(uint32_t frame_token,
                                  base::TimeTicks time,
                                  base::TimeDelta refresh,
                                  uint32_t flags) override;
-  void DidDiscardCompositorFrame(uint32_t presentation_token) override;
+  void DidDiscardCompositorFrame(uint32_t frame_token) override;
   void ReclaimResources(
       const std::vector<viz::ReturnedResource>& resources) override;
   void SetMemoryPolicy(const ManagedMemoryPolicy& policy) override;
@@ -488,6 +491,8 @@
     return &image_animation_controller_;
   }
 
+  uint32_t next_frame_token() const { return next_frame_token_; }
+
   virtual bool WillBeginImplFrame(const viz::BeginFrameArgs& args);
   virtual void DidFinishImplFrame();
   void DidNotProduceFrame(const viz::BeginFrameAck& ack);
@@ -1060,11 +1065,6 @@
   // each CompositorFrame.
   std::unique_ptr<RenderFrameMetadataObserver> render_frame_metadata_observer_;
 
-  // Maps from presentation_token set on CF to the source frame that requested
-  // it. Presentation tokens are requested if the active tree has
-  // request_presentation_time() set.
-  base::flat_map<uint32_t, int> presentation_token_to_frame_;
-
   uint32_t next_frame_token_ = 1u;
 
   viz::LocalSurfaceId last_draw_local_surface_id_;
@@ -1076,6 +1076,10 @@
 
   std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
 
+  base::circular_deque<
+      std::pair<uint32_t, std::vector<LayerTreeHost::PresentationTimeCallback>>>
+      presentation_callbacks_;
+
   DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl);
 };
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 5cac959..3ced46c 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -207,7 +207,8 @@
   void NotifyImageDecodeRequestFinished() override {}
   void RequestBeginMainFrameNotExpected(bool new_state) override {}
   void DidPresentCompositorFrameOnImplThread(
-      const std::vector<int>& source_frames,
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
       base::TimeTicks time,
       base::TimeDelta refresh,
       uint32_t flags) override {}
diff --git a/cc/trees/layer_tree_host_unittest_animation.cc b/cc/trees/layer_tree_host_unittest_animation.cc
index 0a1a2b73..f2626b2 100644
--- a/cc/trees/layer_tree_host_unittest_animation.cc
+++ b/cc/trees/layer_tree_host_unittest_animation.cc
@@ -29,6 +29,7 @@
 #include "cc/trees/effect_node.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/transform_node.h"
+#include "components/viz/common/quads/compositor_frame.h"
 
 namespace cc {
 namespace {
@@ -1007,6 +1008,85 @@
 
 MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestScrollOffsetAnimationAdjusted);
 
+// Tests that presentation-time requested from the main-thread is attached to
+// the correct frame (i.e. activation needs to happen before the
+// presentation-request is attached to the frame).
+class LayerTreeHostPresentationDuringAnimation
+    : public LayerTreeHostAnimationTest {
+ public:
+  void SetupTree() override {
+    LayerTreeHostAnimationTest::SetupTree();
+
+    scroll_layer_ = FakePictureLayer::Create(&client_);
+    scroll_layer_->SetScrollable(gfx::Size(100, 100));
+    scroll_layer_->SetBounds(gfx::Size(10000, 10000));
+    client_.set_bounds(scroll_layer_->bounds());
+    scroll_layer_->SetScrollOffset(gfx::ScrollOffset(100.0, 200.0));
+    layer_tree_host()->root_layer()->AddChild(scroll_layer_);
+
+    std::unique_ptr<ScrollOffsetAnimationCurve> curve(
+        ScrollOffsetAnimationCurve::Create(
+            gfx::ScrollOffset(6500.f, 7500.f),
+            CubicBezierTimingFunction::CreatePreset(
+                CubicBezierTimingFunction::EaseType::EASE_IN_OUT)));
+    std::unique_ptr<KeyframeModel> keyframe_model(KeyframeModel::Create(
+        std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET));
+    keyframe_model->set_needs_synchronized_start_time(true);
+
+    AttachAnimationsToTimeline();
+    animation_child_->AttachElement(scroll_layer_->element_id());
+    animation_child_->AddKeyframeModel(std::move(keyframe_model));
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  void BeginMainFrame(const viz::BeginFrameArgs& args) override {
+    PostSetNeedsCommitToMainThread();
+  }
+
+  void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override {
+    if (host_impl->active_tree()->source_frame_number() == 1) {
+      request_token_ = host_impl->next_frame_token();
+      layer_tree_host()->RequestPresentationTimeForNextFrame(base::BindOnce(
+          &LayerTreeHostPresentationDuringAnimation::OnPresentation,
+          base::Unretained(this)));
+      host_impl->BlockNotifyReadyToActivateForTesting(true);
+    }
+  }
+
+  void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
+                                  const viz::BeginFrameArgs& args) override {
+    if (host_impl->next_frame_token() >= 5)
+      host_impl->BlockNotifyReadyToActivateForTesting(false);
+  }
+
+  void DisplayReceivedCompositorFrameOnThread(
+      const viz::CompositorFrame& frame) override {
+    if (frame.metadata.request_presentation_feedback)
+      received_token_ = frame.metadata.frame_token;
+  }
+
+  void AfterTest() override {
+    EXPECT_GT(request_token_, 0u);
+    EXPECT_GT(received_token_, request_token_);
+    EXPECT_GE(received_token_, 5u);
+  }
+
+ private:
+  void OnPresentation(base::TimeTicks timestamp,
+                      base::TimeDelta refresh,
+                      uint32_t flags) {
+    EndTest();
+  }
+
+  FakeContentLayerClient client_;
+  scoped_refptr<FakePictureLayer> scroll_layer_;
+  uint32_t request_token_ = 0;
+  uint32_t received_token_ = 0;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostPresentationDuringAnimation);
+
 // Verifies that when the main thread removes a scroll animation and sets a new
 // scroll position, the active tree takes on exactly this new scroll position
 // after activation, and the main thread doesn't receive a spurious scroll
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 66b0edd..6967a8a5 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -468,7 +468,6 @@
   target_tree->elastic_overscroll()->PushPendingToActive();
 
   target_tree->set_content_source_id(content_source_id());
-  target_tree->set_request_presentation_time(request_presentation_time());
 
   if (TakeNewLocalSurfaceIdRequest())
     target_tree->RequestNewLocalSurfaceId();
@@ -510,6 +509,7 @@
   // Note: this needs to happen after SetPropertyTrees.
   target_tree->HandleTickmarksVisibilityChange();
   target_tree->HandleScrollbarShowRequestsFromMain();
+  target_tree->AddPresentationCallbacks(std::move(presentation_callbacks_));
 }
 
 void LayerTreeImpl::HandleTickmarksVisibilityChange() {
@@ -666,6 +666,20 @@
     set_needs_update_draw_properties();
 }
 
+void LayerTreeImpl::AddPresentationCallbacks(
+    std::vector<LayerTreeHost::PresentationTimeCallback> callbacks) {
+  std::copy(std::make_move_iterator(callbacks.begin()),
+            std::make_move_iterator(callbacks.end()),
+            std::back_inserter(presentation_callbacks_));
+}
+
+std::vector<LayerTreeHost::PresentationTimeCallback>
+LayerTreeImpl::TakePresentationCallbacks() {
+  std::vector<LayerTreeHost::PresentationTimeCallback> callbacks;
+  callbacks.swap(presentation_callbacks_);
+  return callbacks;
+}
+
 ScrollNode* LayerTreeImpl::CurrentlyScrollingNode() {
   DCHECK(IsActiveTree());
   return property_trees_.scroll_tree.CurrentlyScrollingNode();
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index 95e6fe4..dfc51c5 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -20,6 +20,7 @@
 #include "cc/layers/layer_impl.h"
 #include "cc/layers/layer_list_iterator.h"
 #include "cc/resources/ui_resource_client.h"
+#include "cc/trees/layer_tree_host.h"
 #include "cc/trees/layer_tree_host_impl.h"
 #include "cc/trees/property_tree.h"
 #include "cc/trees/swap_promise.h"
@@ -220,6 +221,14 @@
   gfx::ScrollOffset TotalScrollOffset() const;
   gfx::ScrollOffset TotalMaxScrollOffset() const;
 
+  void AddPresentationCallbacks(
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks);
+  std::vector<LayerTreeHost::PresentationTimeCallback>
+  TakePresentationCallbacks();
+  bool has_presentation_callbacks() const {
+    return !presentation_callbacks_.empty();
+  }
+
   ScrollNode* CurrentlyScrollingNode();
   const ScrollNode* CurrentlyScrollingNode() const;
   int LastScrolledScrollNodeIndex() const;
@@ -565,11 +574,6 @@
 
   LayerTreeLifecycle& lifecycle() { return lifecycle_; }
 
-  bool request_presentation_time() const { return request_presentation_time_; }
-  void set_request_presentation_time(bool value) {
-    request_presentation_time_ = value;
-  }
-
  protected:
   float ClampPageScaleFactorToLimits(float page_scale_factor) const;
   void PushPageScaleFactorAndLimits(const float* page_scale_factor,
@@ -695,9 +699,7 @@
   // lifecycle states. See: |LayerTreeLifecycle|.
   LayerTreeLifecycle lifecycle_;
 
-  // If true LayerTreeHostImpl requests a presentation token for the current
-  // frame.
-  bool request_presentation_time_ = false;
+  std::vector<LayerTreeHost::PresentationTimeCallback> presentation_callbacks_;
 
   DISALLOW_COPY_AND_ASSIGN(LayerTreeImpl);
 };
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 06b0514..b7024e16 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -479,14 +479,15 @@
 }
 
 void ProxyImpl::DidPresentCompositorFrameOnImplThread(
-    const std::vector<int>& source_frames,
+    uint32_t frame_token,
+    std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
     base::TimeTicks time,
     base::TimeDelta refresh,
     uint32_t flags) {
   MainThreadTaskRunner()->PostTask(
       FROM_HERE, base::BindOnce(&ProxyMain::DidPresentCompositorFrame,
-                                proxy_main_weak_ptr_, source_frames, time,
-                                refresh, flags));
+                                proxy_main_weak_ptr_, frame_token,
+                                std::move(callbacks), time, refresh, flags));
 }
 
 bool ProxyImpl::WillBeginImplFrame(const viz::BeginFrameArgs& args) {
diff --git a/cc/trees/proxy_impl.h b/cc/trees/proxy_impl.h
index 467f0e6..312278e 100644
--- a/cc/trees/proxy_impl.h
+++ b/cc/trees/proxy_impl.h
@@ -101,7 +101,8 @@
   void NeedsImplSideInvalidation(bool needs_first_draw_on_activation) override;
   void NotifyImageDecodeRequestFinished() override;
   void DidPresentCompositorFrameOnImplThread(
-      const std::vector<int>& source_frames,
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
       base::TimeTicks time,
       base::TimeDelta refresh,
       uint32_t flags) override;
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index 668e674e..5ea6278 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -331,12 +331,14 @@
   layer_tree_host_->DidBeginMainFrame();
 }
 
-void ProxyMain::DidPresentCompositorFrame(const std::vector<int>& source_frames,
-                                          base::TimeTicks time,
-                                          base::TimeDelta refresh,
-                                          uint32_t flags) {
-  layer_tree_host_->DidPresentCompositorFrame(source_frames, time, refresh,
-                                              flags);
+void ProxyMain::DidPresentCompositorFrame(
+    uint32_t frame_token,
+    std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
+    base::TimeTicks time,
+    base::TimeDelta refresh,
+    uint32_t flags) {
+  layer_tree_host_->DidPresentCompositorFrame(frame_token, std::move(callbacks),
+                                              time, refresh, flags);
 }
 
 bool ProxyMain::IsStarted() const {
diff --git a/cc/trees/proxy_main.h b/cc/trees/proxy_main.h
index b280ca9..320b845 100644
--- a/cc/trees/proxy_main.h
+++ b/cc/trees/proxy_main.h
@@ -52,10 +52,12 @@
   void DidCompletePageScaleAnimation();
   void BeginMainFrame(
       std::unique_ptr<BeginMainFrameAndCommitState> begin_main_frame_state);
-  void DidPresentCompositorFrame(const std::vector<int>& source_frames,
-                                 base::TimeTicks time,
-                                 base::TimeDelta refresh,
-                                 uint32_t flags);
+  void DidPresentCompositorFrame(
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
+      base::TimeTicks time,
+      base::TimeDelta refresh,
+      uint32_t flags);
 
   CommitPipelineStage max_requested_pipeline_stage() const {
     return max_requested_pipeline_stage_;
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 28c45b6..2557840e 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -473,12 +473,13 @@
 }
 
 void SingleThreadProxy::DidPresentCompositorFrameOnImplThread(
-    const std::vector<int>& source_frames,
+    uint32_t frame_token,
+    std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
     base::TimeTicks time,
     base::TimeDelta refresh,
     uint32_t flags) {
-  layer_tree_host_->DidPresentCompositorFrame(source_frames, time, refresh,
-                                              flags);
+  layer_tree_host_->DidPresentCompositorFrame(frame_token, std::move(callbacks),
+                                              time, refresh, flags);
 }
 
 void SingleThreadProxy::RequestBeginMainFrameNotExpected(bool new_state) {
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index f505320..25996914 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -122,7 +122,8 @@
   void RequestBeginMainFrameNotExpected(bool new_state) override;
   void NotifyImageDecodeRequestFinished() override;
   void DidPresentCompositorFrameOnImplThread(
-      const std::vector<int>& source_frames,
+      uint32_t frame_token,
+      std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
       base::TimeTicks time,
       base::TimeDelta refresh,
       uint32_t flags) override;
diff --git a/chrome/VERSION b/chrome/VERSION
index c6f4a889..650ec64 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=69
 MINOR=0
-BUILD=3448
+BUILD=3449
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
index 519e5c1d..fb153a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
@@ -45,6 +45,7 @@
     private final boolean mIsOfflinePage;
     private final int mState;
     private final long mLastAccessTime;
+    private final boolean mIsDangerous;
 
     // New variables to assist with the migration to OfflineItems.
     private final ContentId mContentId;
@@ -79,6 +80,7 @@
         mIsOfflinePage = builder.mIsOfflinePage;
         mState = builder.mState;
         mLastAccessTime = builder.mLastAccessTime;
+        mIsDangerous = builder.mIsDangerous;
 
         if (builder.mContentId != null) {
             mContentId = builder.mContentId;
@@ -187,6 +189,10 @@
         return mLastAccessTime;
     }
 
+    public boolean getIsDangerous() {
+        return mIsDangerous;
+    }
+
     public ContentId getContentId() {
         return mContentId;
     }
@@ -258,6 +264,7 @@
                 .setBytesTotalSize(item.totalSizeBytes)
                 .setProgress(item.progress)
                 .setTimeRemainingInMillis(item.timeRemainingMs)
+                .setIsDangerous(item.isDangerous)
                 .setIsParallelDownload(item.isAccelerated)
                 .setIcon(visuals == null ? null : visuals.icon)
                 .setPendingState(item.pendingState)
@@ -287,6 +294,7 @@
         offlineItem.isOffTheRecord = downloadInfo.isOffTheRecord();
         offlineItem.mimeType = downloadInfo.getMimeType();
         offlineItem.progress = downloadInfo.getProgress();
+        offlineItem.isDangerous = downloadInfo.getIsDangerous();
         switch (downloadInfo.state()) {
             case DownloadState.IN_PROGRESS:
                 offlineItem.state = downloadInfo.isPaused() ? OfflineItemState.PAUSED
@@ -337,6 +345,7 @@
         private boolean mIsOfflinePage;
         private int mState = DownloadState.IN_PROGRESS;
         private long mLastAccessTime;
+        private boolean mIsDangerous;
         private ContentId mContentId;
         private boolean mIsOpenable = true;
         private boolean mIsTransient;
@@ -460,6 +469,11 @@
             return this;
         }
 
+        public Builder setIsDangerous(boolean isDangerous) {
+            mIsDangerous = isDangerous;
+            return this;
+        }
+
         public Builder setContentId(ContentId contentId) {
             mContentId = contentId;
             return this;
@@ -518,6 +532,7 @@
                     .setIsGETRequest(downloadInfo.isGETRequest())
                     .setProgress(downloadInfo.getProgress())
                     .setTimeRemainingInMillis(downloadInfo.getTimeRemainingInMillis())
+                    .setIsDangerous(downloadInfo.getIsDangerous())
                     .setIsResumable(downloadInfo.isResumable())
                     .setIsPaused(downloadInfo.isPaused())
                     .setIsOffTheRecord(downloadInfo.isOffTheRecord())
@@ -537,7 +552,8 @@
             String filePath, String url, String mimeType, long bytesReceived, long bytesTotalSize,
             boolean isIncognito, int state, int percentCompleted, boolean isPaused,
             boolean hasUserGesture, boolean isResumable, boolean isParallelDownload,
-            String originalUrl, String referrerUrl, long timeRemainingInMs, long lastAccessTime) {
+            String originalUrl, String referrerUrl, long timeRemainingInMs, long lastAccessTime,
+            boolean isDangerous) {
         String remappedMimeType = ChromeDownloadDelegate.remapGenericMimeType(
                 mimeType, url, fileName);
 
@@ -563,6 +579,7 @@
                 .setState(state)
                 .setTimeRemainingInMillis(timeRemainingInMs)
                 .setLastAccessTime(lastAccessTime)
+                .setIsDangerous(isDangerous)
                 .setUrl(url)
                 .build();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index 9310324..85ab68f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -340,6 +340,7 @@
         if (offlineItem.isTransient) return false;
         if (offlineItem.isOffTheRecord != mIsIncognito) return false;
         if (offlineItem.isSuggested) return false;
+        if (offlineItem.isDangerous) return false;
         if (LegacyHelpers.isLegacyDownload(offlineItem.id)) {
             if (TextUtils.isEmpty(offlineItem.filePath)) {
                 return false;
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 6b321fd..ddec348 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-69.0.3447.0_rc-r1.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-69.0.3448.0_rc-r1.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/app-Info.plist b/chrome/app/app-Info.plist
index 52a6c5c..94f0c91 100644
--- a/chrome/app/app-Info.plist
+++ b/chrome/app/app-Info.plist
@@ -319,7 +319,7 @@
 	<key>LSHasLocalizedDisplayName</key>
 	<string>1</string>
 	<key>LSMinimumSystemVersion</key>
-	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>NSPrincipalClass</key>
 	<string>BrowserCrApplication</string>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 9e640a5b..d487887 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4790,6 +4790,9 @@
       </if>
 
       <!-- Omnibox -->
+      <message name="IDS_OMNIBOX_PLACEHOLDER_TEXT" desc="The text displayed in the Omnibox when it is empty and unfocused.">
+        Search <ph name="SEARCH_ENGINE_NAME">$1<ex>Google</ex></ph> or type a web address
+      </message>
       <if expr="not use_titlecase">
         <message name="IDS_PASTE_AND_GO" desc="The text label of the Paste And Go menu item when the clipboard contains a URL">
           Pa&amp;ste and go
@@ -4847,7 +4850,7 @@
         Search Google or type URL
       </message>
       <message name="IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT_MD" desc="The text displayed in the fakebox (on the New Tab page) when it is empty, Google is the default search engine, and the Material Design UI is enabled.">
-        Search Google or type a URL
+        Search Google or type a web address
       </message>
       <message name="IDS_NTP_CUSTOM_LINKS_ADD_SHORTCUT_TOOLTIP" desc="The tooltip for adding a custom link shortcut. (On the New Tab Page)">
         Add shortcut
diff --git a/chrome/app/helper-Info.plist b/chrome/app/helper-Info.plist
index e210c35..f592427 100644
--- a/chrome/app/helper-Info.plist
+++ b/chrome/app/helper-Info.plist
@@ -21,7 +21,7 @@
 	<key>LSFileQuarantineEnabled</key>
 	<true/>
 	<key>LSMinimumSystemVersion</key>
-	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSUIElement</key>
 	<string>1</string>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
diff --git a/chrome/app_shim/app_mode-Info.plist b/chrome/app_shim/app_mode-Info.plist
index 9442527..cdacd922 100644
--- a/chrome/app_shim/app_mode-Info.plist
+++ b/chrome/app_shim/app_mode-Info.plist
@@ -27,7 +27,7 @@
        <key>CrBundleIdentifier</key>
        <string>@APP_MODE_BROWSER_BUNDLE_ID@</string>
        <key>LSMinimumSystemVersion</key>
-       <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+       <string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
        <key>NSAppleScriptEnabled</key>
        <true/>
 </dict>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e42cec0..524be88f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1513,7 +1513,7 @@
      FEATURE_VALUE_TYPE(features::kMultidevice)},
     {"multidevice-service", flag_descriptions::kMultiDeviceApiName,
      flag_descriptions::kMultiDeviceApiDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kMultiDeviceApi)},
+     FEATURE_VALUE_TYPE(chromeos::features::kMultiDeviceApi)},
     {"mash", flag_descriptions::kUseMashName,
      flag_descriptions::kUseMashDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kMash)},
diff --git a/chrome/browser/android/download/download_manager_service.cc b/chrome/browser/android/download/download_manager_service.cc
index a3889c0..63838247 100644
--- a/chrome/browser/android/download/download_manager_service.cc
+++ b/chrome/browser/android/download/download_manager_service.cc
@@ -119,7 +119,7 @@
       ConvertUTF8ToJavaString(env, item->GetReferrerUrl().spec()),
       time_remaining_known ? time_delta.InMilliseconds()
                            : kUnknownRemainingTime,
-      item->GetLastAccessTime().ToJavaTime());
+      item->GetLastAccessTime().ToJavaTime(), item->IsDangerous());
 }
 
 static jlong JNI_DownloadManagerService_Init(
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 1f76f4d4d..1897908e 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -630,6 +630,11 @@
   Profile* profile2 = profile_manager->GetProfileByPath(profile2_path);
   ASSERT_TRUE(profile2);
 
+  // Load profile1's History Service backend so it will be assigned to the
+  // HistoryMenuBridge when windowChangedToProfile is called, or else this test
+  // will fail flaky.
+  ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
+      profile1, ServiceAccessType::EXPLICIT_ACCESS));
   // Switch the controller to profile1.
   [ac windowChangedToProfile:profile1];
   base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index da122b49..489bb99f 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -61,6 +61,7 @@
 #include "chrome/browser/nacl_host/nacl_browser_delegate_impl.h"
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
+#include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/net_benchmarking.h"
 #include "chrome/browser/notifications/platform_notification_service_impl.h"
 #include "chrome/browser/page_load_metrics/metrics_navigation_throttle.h"
@@ -4192,6 +4193,13 @@
 #endif
 }
 
+void ChromeContentBrowserClient::OnNetworkServiceCreated(
+    network::mojom::NetworkService* network_service) {
+  // Need to set up global NetworkService state before anything else uses it.
+  g_browser_process->system_network_context_manager()->OnNetworkServiceCreated(
+      network_service);
+}
+
 network::mojom::NetworkContextPtr
 ChromeContentBrowserClient::CreateNetworkContext(
     content::BrowserContext* context,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index af4e461..52aeece 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -24,7 +24,7 @@
 #include "extensions/buildflags/buildflags.h"
 #include "media/media_buildflags.h"
 #include "ppapi/buildflags/buildflags.h"
-#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "ui/base/resource/data_pack.h"
 
@@ -401,7 +401,8 @@
       int frame_tree_node_id) override;
   void WillCreateWebSocket(content::RenderFrameHost* frame,
                            network::mojom::WebSocketRequest* request) override;
-
+  void OnNetworkServiceCreated(
+      network::mojom::NetworkService* network_service) override;
   network::mojom::NetworkContextPtr CreateNetworkContext(
       content::BrowserContext* context,
       bool in_memory,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 7640d90..406fddd 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2204,6 +2204,7 @@
     "//chromeos:login_manager_proto",
     "//chromeos/components/tether:test_support",
     "//chromeos/ime:gencode",
+    "//chromeos/services/device_sync/public/cpp:test_support",
     "//components/cryptauth:test_support",
     "//components/drive",
     "//components/drive:drive_chromeos",
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
index df61b3f..b8d93f2c 100644
--- a/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
+++ b/chrome/browser/chromeos/arc/auth/arc_auth_service.cc
@@ -23,6 +23,7 @@
 #include "components/arc/arc_features.h"
 #include "components/arc/arc_prefs.h"
 #include "components/arc/arc_service_manager.h"
+#include "components/arc/arc_supervision_transition.h"
 #include "components/arc/arc_util.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user_manager.h"
@@ -206,6 +207,27 @@
   UpdateAuthAccountCheckStatus(status);
 }
 
+void ArcAuthService::ReportSupervisionChangeStatus(
+    mojom::SupervisionChangeStatus status) {
+  switch (status) {
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_DISABLED:
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_ALREADY_DISABLED:
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_ENABLED:
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_ALREADY_ENABLED:
+      profile_->GetPrefs()->SetInteger(
+          prefs::kArcSupervisionTransition,
+          static_cast<int>(ArcSupervisionTransition::NO_TRANSITION));
+      // TODO(brunokim): notify potential observers.
+      break;
+    case mojom::SupervisionChangeStatus::INVALID_SUPERVISION_STATE:
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_DISABLING_FAILED:
+    case mojom::SupervisionChangeStatus::CLOUD_DPC_ENABLING_FAILED:
+    default:
+      LOG(WARNING) << "Failed to changed supervision: " << status;
+      // TODO(crbug/841939): Block ARC++ in case of Unicorn graduation failure.
+  }
+}
+
 void ArcAuthService::OnAccountInfoReady(mojom::AccountInfoPtr account_info,
                                         mojom::ArcSignInStatus status) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/chromeos/arc/auth/arc_auth_service.h b/chrome/browser/chromeos/arc/auth/arc_auth_service.h
index bd06cb17..aaa0f5b 100644
--- a/chrome/browser/chromeos/arc/auth/arc_auth_service.h
+++ b/chrome/browser/chromeos/arc/auth/arc_auth_service.h
@@ -58,6 +58,8 @@
   void RequestAccountInfo(bool initial_signin) override;
   void ReportMetrics(mojom::MetricsType metrics_type, int32_t value) override;
   void ReportAccountCheckStatus(mojom::AccountCheckStatus status) override;
+  void ReportSupervisionChangeStatus(
+      mojom::SupervisionChangeStatus status) override;
 
   void SetURLLoaderFactoryForTesting(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
diff --git a/chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.cc b/chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.cc
index 72acbc37..22bfe39 100644
--- a/chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.cc
+++ b/chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
-#include "chrome/common/chrome_features.h"
+#include "chromeos/chromeos_features.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 
@@ -43,7 +43,7 @@
     content::BrowserContext* context) const {
   // If DeviceSync Mojo Service is being used to get remote device information,
   // CryptAuthService is not needed, and should not be used.
-  if (base::FeatureList::IsEnabled(features::kMultiDeviceApi))
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi))
     return nullptr;
 
   Profile* profile = Profile::FromBrowserContext(context);
diff --git a/chrome/browser/chromeos/device_sync/device_sync_client_factory.cc b/chrome/browser/chromeos/device_sync/device_sync_client_factory.cc
index 97fdc71..7cc64a0 100644
--- a/chrome/browser/chromeos/device_sync/device_sync_client_factory.cc
+++ b/chrome/browser/chromeos/device_sync/device_sync_client_factory.cc
@@ -8,8 +8,8 @@
 #include "chrome/browser/gcm/gcm_profile_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
+#include "chromeos/chromeos_features.h"
 #include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/services/device_sync/public/cpp/device_sync_client_impl.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -37,7 +37,7 @@
 class DeviceSyncClientHolder : public KeyedService {
  public:
   explicit DeviceSyncClientHolder(content::BrowserContext* context)
-      : device_sync_client_(std::make_unique<DeviceSyncClientImpl>(
+      : device_sync_client_(DeviceSyncClientImpl::Factory::Get()->BuildInstance(
             content::BrowserContext::GetConnectorFor(context))) {}
 
   DeviceSyncClient* device_sync_client() { return device_sync_client_.get(); }
@@ -76,7 +76,7 @@
   // TODO(crbug.com/848347): Check prohibited by policy in services that depend
   // on this Factory, not here.
   if (IsEnrollmentAllowedByPolicy(context) &&
-      base::FeatureList::IsEnabled(features::kMultiDeviceApi)) {
+      base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
     return new DeviceSyncClientHolder(context);
   }
 
@@ -85,4 +85,4 @@
 
 }  // namespace device_sync
 
-}  // namespace chromeos
\ No newline at end of file
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/tether/fake_tether_service.cc b/chrome/browser/chromeos/tether/fake_tether_service.cc
index 5b3af8ff..b435fb59 100644
--- a/chrome/browser/chromeos/tether/fake_tether_service.cc
+++ b/chrome/browser/chromeos/tether/fake_tether_service.cc
@@ -12,11 +12,13 @@
     Profile* profile,
     chromeos::PowerManagerClient* power_manager_client,
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     chromeos::NetworkStateHandler* network_state_handler,
     session_manager::SessionManager* session_manager)
     : TetherService(profile,
                     power_manager_client,
                     cryptauth_service,
+                    device_sync_client,
                     network_state_handler,
                     session_manager) {}
 
diff --git a/chrome/browser/chromeos/tether/fake_tether_service.h b/chrome/browser/chromeos/tether/fake_tether_service.h
index 5cb9a00..4b4856c 100644
--- a/chrome/browser/chromeos/tether/fake_tether_service.h
+++ b/chrome/browser/chromeos/tether/fake_tether_service.h
@@ -15,6 +15,7 @@
   FakeTetherService(Profile* profile,
                     chromeos::PowerManagerClient* power_manager_client,
                     cryptauth::CryptAuthService* cryptauth_service,
+                    chromeos::device_sync::DeviceSyncClient* device_sync_client,
                     chromeos::NetworkStateHandler* network_state_handler,
                     session_manager::SessionManager* session_manager);
 
diff --git a/chrome/browser/chromeos/tether/tether_service.cc b/chrome/browser/chromeos/tether/tether_service.cc
index a5c8c21..fcd5b203 100644
--- a/chrome/browser/chromeos/tether/tether_service.cc
+++ b/chrome/browser/chromeos/tether/tether_service.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/ash/network/tether_notification_presenter.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
+#include "chromeos/chromeos_features.h"
 #include "chromeos/chromeos_switches.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/components/tether/gms_core_notifications_state_tracker_impl.h"
@@ -25,6 +26,7 @@
 #include "chromeos/network/device_state.h"
 #include "chromeos/network/network_connect.h"
 #include "chromeos/network/network_type_pattern.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "components/cryptauth/cryptauth_enrollment_manager.h"
 #include "components/cryptauth/cryptauth_service.h"
 #include "components/cryptauth/remote_device_provider_impl.h"
@@ -125,11 +127,13 @@
     Profile* profile,
     chromeos::PowerManagerClient* power_manager_client,
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     chromeos::NetworkStateHandler* network_state_handler,
     session_manager::SessionManager* session_manager)
     : profile_(profile),
       power_manager_client_(power_manager_client),
       cryptauth_service_(cryptauth_service),
+      device_sync_client_(device_sync_client),
       network_state_handler_(network_state_handler),
       session_manager_(session_manager),
       notification_presenter_(
@@ -140,14 +144,17 @@
           std::make_unique<
               chromeos::tether::GmsCoreNotificationsStateTrackerImpl>()),
       remote_device_provider_(
-          cryptauth::RemoteDeviceProviderImpl::Factory::NewInstance(
-              cryptauth_service->GetCryptAuthDeviceManager(),
-              cryptauth_service->GetAccountId(),
-              cryptauth_service->GetCryptAuthEnrollmentManager()
-                  ->GetUserPrivateKey())),
+          base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
+              ? nullptr
+              : cryptauth::RemoteDeviceProviderImpl::Factory::NewInstance(
+                    cryptauth_service->GetCryptAuthDeviceManager(),
+                    cryptauth_service->GetAccountId(),
+                    cryptauth_service->GetCryptAuthEnrollmentManager()
+                        ->GetUserPrivateKey())),
       tether_host_fetcher_(
           chromeos::tether::TetherHostFetcherImpl::Factory::NewInstance(
-              remote_device_provider_.get())),
+              remote_device_provider_.get(),
+              device_sync_client_)),
       timer_(std::make_unique<base::OneShotTimer>()),
       weak_ptr_factory_(this) {
   tether_host_fetcher_->AddObserver(this);
@@ -192,7 +199,7 @@
   PA_LOG(INFO) << "Starting up TetherComponent.";
   tether_component_ =
       chromeos::tether::TetherComponentImpl::Factory::NewInstance(
-          cryptauth_service_, tether_host_fetcher_.get(),
+          cryptauth_service_, device_sync_client_, tether_host_fetcher_.get(),
           notification_presenter_.get(),
           gms_core_notifications_state_tracker_.get(), profile_->GetPrefs(),
           network_state_handler_,
diff --git a/chrome/browser/chromeos/tether/tether_service.h b/chrome/browser/chromeos/tether/tether_service.h
index 55b0614..2490a43 100644
--- a/chrome/browser/chromeos/tether/tether_service.h
+++ b/chrome/browser/chromeos/tether/tether_service.h
@@ -25,6 +25,9 @@
 
 namespace chromeos {
 class NetworkStateHandler;
+namespace device_sync {
+class DeviceSyncClient;
+}  // namespace device_sync
 namespace tether {
 class GmsCoreNotificationsStateTracker;
 class GmsCoreNotificationsStateTrackerImpl;
@@ -61,6 +64,7 @@
   TetherService(Profile* profile,
                 chromeos::PowerManagerClient* power_manager_client,
                 cryptauth::CryptAuthService* cryptauth_service,
+                chromeos::device_sync::DeviceSyncClient* device_sync_client,
                 chromeos::NetworkStateHandler* network_state_handler,
                 session_manager::SessionManager* session_manager);
   ~TetherService() override;
@@ -138,6 +142,9 @@
                            TestBleAdvertisingSupportedButIncorrectlyRecorded);
   FRIEND_TEST_ALL_PREFIXES(TetherServiceTest,
                            TestGet_PrimaryUser_FeatureFlagEnabled);
+  FRIEND_TEST_ALL_PREFIXES(
+      TetherServiceTest,
+      TestGet_PrimaryUser_FeatureFlagEnabled_MultiDeviceApiFlagEnabled);
   FRIEND_TEST_ALL_PREFIXES(TetherServiceTest, TestNoTetherHosts);
   FRIEND_TEST_ALL_PREFIXES(TetherServiceTest, TestProhibitedByPolicy);
   FRIEND_TEST_ALL_PREFIXES(TetherServiceTest, TestIsBluetoothPowered);
@@ -254,6 +261,7 @@
   Profile* profile_;
   chromeos::PowerManagerClient* power_manager_client_;
   cryptauth::CryptAuthService* cryptauth_service_;
+  chromeos::device_sync::DeviceSyncClient* device_sync_client_;
   chromeos::NetworkStateHandler* network_state_handler_;
   session_manager::SessionManager* session_manager_;
   std::unique_ptr<chromeos::tether::NotificationPresenter>
diff --git a/chrome/browser/chromeos/tether/tether_service_factory.cc b/chrome/browser/chromeos/tether/tether_service_factory.cc
index 774a83c..e1c2cdc 100644
--- a/chrome/browser/chromeos/tether/tether_service_factory.cc
+++ b/chrome/browser/chromeos/tether/tether_service_factory.cc
@@ -8,6 +8,7 @@
 #include "base/memory/singleton.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.h"
+#include "chrome/browser/chromeos/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/chromeos/tether/fake_tether_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
@@ -37,6 +38,7 @@
           "TetherService",
           BrowserContextDependencyManager::GetInstance()) {
   DependsOn(chromeos::ChromeCryptAuthServiceFactory::GetInstance());
+  DependsOn(chromeos::device_sync::DeviceSyncClientFactory::GetInstance());
 }
 
 TetherServiceFactory::~TetherServiceFactory() {}
@@ -52,6 +54,8 @@
         chromeos::DBusThreadManager::Get()->GetPowerManagerClient(),
         chromeos::ChromeCryptAuthServiceFactory::GetForBrowserContext(
             Profile::FromBrowserContext(context)),
+        chromeos::device_sync::DeviceSyncClientFactory::GetForProfile(
+            Profile::FromBrowserContext(context)),
         chromeos::NetworkHandler::Get()->network_state_handler(),
         session_manager::SessionManager::Get());
 
@@ -69,6 +73,8 @@
       chromeos::DBusThreadManager::Get()->GetPowerManagerClient(),
       chromeos::ChromeCryptAuthServiceFactory::GetForBrowserContext(
           Profile::FromBrowserContext(context)),
+      chromeos::device_sync::DeviceSyncClientFactory::GetForProfile(
+          Profile::FromBrowserContext(context)),
       chromeos::NetworkHandler::Get()->network_state_handler(),
       session_manager::SessionManager::Get());
 }
diff --git a/chrome/browser/chromeos/tether/tether_service_unittest.cc b/chrome/browser/chromeos/tether/tether_service_unittest.cc
index b4bf0054..07742c9 100644
--- a/chrome/browser/chromeos/tether/tether_service_unittest.cc
+++ b/chrome/browser/chromeos/tether/tether_service_unittest.cc
@@ -21,6 +21,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/chromeos_features.h"
 #include "chromeos/chromeos_switches.h"
 #include "chromeos/components/tether/fake_notification_presenter.h"
 #include "chromeos/components/tether/fake_tether_component.h"
@@ -38,6 +39,8 @@
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test.h"
 #include "chromeos/network/network_type_pattern.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client_impl.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "components/cryptauth/cryptauth_device_manager.h"
 #include "components/cryptauth/cryptauth_enroller.h"
 #include "components/cryptauth/cryptauth_enrollment_manager.h"
@@ -106,11 +109,13 @@
   TestTetherService(Profile* profile,
                     chromeos::PowerManagerClient* power_manager_client,
                     cryptauth::CryptAuthService* cryptauth_service,
+                    chromeos::device_sync::DeviceSyncClient* device_sync_client,
                     chromeos::NetworkStateHandler* network_state_handler,
                     session_manager::SessionManager* session_manager)
       : TetherService(profile,
                       power_manager_client,
                       cryptauth_service,
+                      device_sync_client,
                       network_state_handler,
                       session_manager) {}
   ~TestTetherService() override {}
@@ -159,6 +164,7 @@
   // chromeos::tether::TetherComponentImpl::Factory:
   std::unique_ptr<chromeos::tether::TetherComponent> BuildInstance(
       cryptauth::CryptAuthService* cryptauth_service,
+      chromeos::device_sync::DeviceSyncClient* device_sync_client,
       chromeos::tether::TetherHostFetcher* tether_host_fetcher,
       chromeos::tether::NotificationPresenter* notification_presenter,
       chromeos::tether::GmsCoreNotificationsStateTrackerImpl*
@@ -227,7 +233,8 @@
 
   // chromeos::tether::TetherHostFetcherImpl::Factory :
   std::unique_ptr<chromeos::tether::TetherHostFetcher> BuildInstance(
-      cryptauth::RemoteDeviceProvider* remote_device_provider) override {
+      cryptauth::RemoteDeviceProvider* remote_device_provider,
+      chromeos::device_sync::DeviceSyncClient* device_sync_client) override {
     last_created_ =
         new chromeos::tether::FakeTetherHostFetcher(initial_devices_);
     return base::WrapUnique(last_created_);
@@ -238,6 +245,20 @@
   chromeos::tether::FakeTetherHostFetcher* last_created_ = nullptr;
 };
 
+class FakeDeviceSyncClientImplFactory
+    : public chromeos::device_sync::DeviceSyncClientImpl::Factory {
+ public:
+  FakeDeviceSyncClientImplFactory() = default;
+
+  ~FakeDeviceSyncClientImplFactory() override = default;
+
+  // chromeos::device_sync::DeviceSyncClientImpl::Factory:
+  std::unique_ptr<chromeos::device_sync::DeviceSyncClient> BuildInstance(
+      service_manager::Connector* connector) override {
+    return std::make_unique<chromeos::device_sync::FakeDeviceSyncClient>();
+  }
+};
+
 }  // namespace
 
 class TetherServiceTest : public chromeos::NetworkStateTest {
@@ -266,6 +287,14 @@
     fake_power_manager_client_ =
         std::make_unique<chromeos::FakePowerManagerClient>();
 
+    fake_device_sync_client_ =
+        std::make_unique<chromeos::device_sync::FakeDeviceSyncClient>();
+
+    fake_device_sync_client_impl_factory_ =
+        std::make_unique<FakeDeviceSyncClientImplFactory>();
+    chromeos::device_sync::DeviceSyncClientImpl::Factory::SetInstanceForTesting(
+        fake_device_sync_client_impl_factory_.get());
+
     fake_cryptauth_service_ =
         std::make_unique<cryptauth::FakeCryptAuthService>();
     fake_enrollment_manager_ =
@@ -307,6 +336,9 @@
   void TearDown() override {
     TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
 
+    chromeos::device_sync::DeviceSyncClientImpl::Factory::SetInstanceForTesting(
+        nullptr);
+
     ShutdownTetherService();
 
     if (tether_service_) {
@@ -342,8 +374,8 @@
   void CreateTetherService() {
     tether_service_ = base::WrapUnique(new TestTetherService(
         profile_.get(), fake_power_manager_client_.get(),
-        fake_cryptauth_service_.get(), network_state_handler(),
-        nullptr /* session_manager */));
+        fake_cryptauth_service_.get(), fake_device_sync_client_.get(),
+        network_state_handler(), nullptr /* session_manager */));
 
     fake_notification_presenter_ =
         new chromeos::tether::FakeNotificationPresenter();
@@ -451,6 +483,10 @@
       fake_tether_host_fetcher_factory_;
   chromeos::tether::FakeNotificationPresenter* fake_notification_presenter_;
   base::MockTimer* mock_timer_;
+  std::unique_ptr<chromeos::device_sync::DeviceSyncClient>
+      fake_device_sync_client_;
+  std::unique_ptr<FakeDeviceSyncClientImplFactory>
+      fake_device_sync_client_impl_factory_;
   std::unique_ptr<cryptauth::FakeCryptAuthService> fake_cryptauth_service_;
   std::unique_ptr<cryptauth::FakeCryptAuthEnrollmentManager>
       fake_enrollment_manager_;
@@ -695,6 +731,26 @@
       chromeos::tether::TetherComponent::ShutdownReason::USER_LOGGED_OUT);
 }
 
+TEST_F(TetherServiceTest,
+       TestGet_PrimaryUser_FeatureFlagEnabled_MultiDeviceApiFlagEnabled) {
+  SetPrimaryUserLoggedIn();
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {features::kInstantTethering,
+       chromeos::features::kMultiDeviceApi} /* enabled_features */,
+      {} /* disabled_features */);
+
+  TetherService* tether_service = TetherService::Get(profile_.get());
+  ASSERT_TRUE(tether_service);
+
+  base::RunLoop().RunUntilIdle();
+  tether_service->Shutdown();
+
+  VerifyLastShutdownReason(
+      chromeos::tether::TetherComponent::ShutdownReason::USER_LOGGED_OUT);
+}
+
 TEST_F(TetherServiceTest, TestNoTetherHosts) {
   fake_tether_host_fetcher_factory_->SetNoInitialDevices();
   CreateTetherService();
diff --git a/chrome/browser/component_updater/subresource_filter_component_installer_unittest.cc b/chrome/browser/component_updater/subresource_filter_component_installer_unittest.cc
index 823a411..ded1e33 100644
--- a/chrome/browser/component_updater/subresource_filter_component_installer_unittest.cc
+++ b/chrome/browser/component_updater/subresource_filter_component_installer_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/run_loop.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/version.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -187,8 +188,9 @@
 
 TEST_F(SubresourceFilterComponentInstallerTest,
        TestComponentRegistrationWhenFeatureDisabled) {
-  subresource_filter::testing::ScopedSubresourceFilterFeatureToggle
-      scoped_feature(base::FeatureList::OVERRIDE_DISABLE_FEATURE);
+  base::test::ScopedFeatureList scoped_disable;
+  scoped_disable.InitAndDisableFeature(
+      subresource_filter::kSafeBrowsingSubresourceFilter);
   std::unique_ptr<SubresourceFilterMockComponentUpdateService>
       component_updater(new SubresourceFilterMockComponentUpdateService());
   EXPECT_CALL(*component_updater, RegisterComponent(testing::_)).Times(0);
@@ -198,8 +200,9 @@
 
 TEST_F(SubresourceFilterComponentInstallerTest,
        TestComponentRegistrationWhenFeatureEnabled) {
-  subresource_filter::testing::ScopedSubresourceFilterFeatureToggle
-      scoped_feature(base::FeatureList::OVERRIDE_ENABLE_FEATURE);
+  base::test::ScopedFeatureList scoped_enable;
+  scoped_enable.InitAndEnableFeature(
+      subresource_filter::kSafeBrowsingSubresourceFilter);
   std::unique_ptr<SubresourceFilterMockComponentUpdateService>
       component_updater(new SubresourceFilterMockComponentUpdateService());
   EXPECT_CALL(*component_updater, RegisterComponent(testing::_))
diff --git a/chrome/browser/extensions/api/runtime/runtime_apitest.cc b/chrome/browser/extensions/api/runtime/runtime_apitest.cc
index b26ef11..d2201559 100644
--- a/chrome/browser/extensions/api/runtime/runtime_apitest.cc
+++ b/chrome/browser/extensions/api/runtime/runtime_apitest.cc
@@ -214,7 +214,14 @@
     ASSERT_TRUE(extension_v1);
     EXPECT_TRUE(catcher.GetNextResult());
   }
+
   ASSERT_TRUE(CrashEnabledExtension(extension_id));
+
+  // The process-terminated notification may be received immediately before
+  // the task that will actually update the active-extensions count, so spin
+  // the message loop to ensure we are up-to-date.
+  base::RunLoop().RunUntilIdle();
+
   {
     // Update to version 2, expect runtime.onInstalled with
     // previousVersion = '1'.
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index 82ffe51c..bda183bd 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -153,6 +153,11 @@
 
 bool ChromeVirtualKeyboardDelegate::ShowLanguageSettings() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  keyboard::KeyboardController* controller =
+      keyboard::KeyboardController::GetInstance();
+  if (controller) {
+    controller->DismissVirtualKeyboard();
+  }
   base::RecordAction(base::UserMetricsAction("OpenLanguageOptionsDialog"));
   chrome::ShowSettingsSubPageForProfile(ProfileManager::GetActiveUserProfile(),
                                         chrome::kLanguageOptionsSubPage);
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index a4bb91d9..0c4d73b2 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -100,10 +100,6 @@
 #include "chrome/browser/extensions/event_router_forwarder.h"
 #endif
 
-#if defined(USE_NSS_CERTS)
-#include "net/cert_net/nss_ocsp.h"
-#endif
-
 #if defined(OS_ANDROID)
 #include "base/android/build_info.h"
 #include "chrome/browser/android/data_usage/external_data_use_observer.h"
@@ -123,12 +119,6 @@
 #include "third_party/boringssl/src/include/openssl/cpu.h"
 #endif
 
-#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
-#include "net/cert/cert_net_fetcher.h"
-#include "net/cert_net/cert_net_fetcher_impl.h"
-#endif
-
 using content::BrowserThread;
 
 class SafeBrowsingURLRequestContext;
@@ -523,15 +513,6 @@
 
   globals_->system_request_context->proxy_resolution_service()->OnShutdown();
 
-#if defined(USE_NSS_CERTS)
-  net::SetURLRequestContextForNSSHttpIO(nullptr);
-#endif
-
-#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
-  net::ShutdownGlobalCertNetFetcher();
-#endif
-
   // Release objects that the net::URLRequestContext could have been pointing
   // to.
 
@@ -747,15 +728,6 @@
   // NetworkQualityEstimator.  Fix that.
   globals_->network_quality_observer = content::CreateNetworkQualityObserver(
       globals_->system_request_context->network_quality_estimator());
-
-#if defined(USE_NSS_CERTS)
-  net::SetURLRequestContextForNSSHttpIO(globals_->system_request_context);
-#endif
-#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
-    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
-  net::SetGlobalCertNetFetcher(
-      net::CreateCertNetFetcher(globals_->system_request_context));
-#endif
 }
 
 metrics::UpdateUsagePrefCallbackType IOThread::GetMetricsDataUseForwarder() {
diff --git a/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc b/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc
index 91eb6b43..46fd696 100644
--- a/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc
+++ b/chrome/browser/media_galleries/fileapi/media_file_system_backend.cc
@@ -134,9 +134,10 @@
 MediaFileSystemBackend::MediaFileSystemBackend(
     const base::FilePath& profile_path)
     : profile_path_(profile_path),
-      media_path_filter_(new MediaPathFilter),
-      media_copy_or_move_file_validator_factory_(new MediaFileValidatorFactory),
-      native_media_file_util_(new NativeMediaFileUtil(media_path_filter_.get()))
+      media_copy_or_move_file_validator_factory_(
+          std::make_unique<MediaFileValidatorFactory>()),
+      native_media_file_util_(
+          std::make_unique<NativeMediaFileUtil>(g_media_task_runner.Get()))
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
       ,
       device_media_async_file_util_(
diff --git a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
index d750102..68853c4f 100644
--- a/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
+++ b/chrome/browser/media_galleries/fileapi/media_file_validator_browsertest.cc
@@ -106,6 +106,14 @@
   }
 
  private:
+  // BrowserTestBase interface.
+  void PostRunTestOnMainThread() override {
+    // Trigger release of the FileSystemContext before the IO thread is gone,
+    // so it can teardown there correctly.
+    file_system_context_ = nullptr;
+    InProcessBrowserTest::PostRunTestOnMainThread();
+  }
+
   // Create the test files, filesystem objects, etc.
   void SetupBlocking(const std::string& filename,
                      const std::string& content,
diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util.cc b/chrome/browser/media_galleries/fileapi/native_media_file_util.cc
index b8d5ffa0..4727633 100644
--- a/chrome/browser/media_galleries/fileapi/native_media_file_util.cc
+++ b/chrome/browser/media_galleries/fileapi/native_media_file_util.cc
@@ -65,12 +65,116 @@
 
 }  // namespace
 
-NativeMediaFileUtil::NativeMediaFileUtil(MediaPathFilter* media_path_filter)
-    : media_path_filter_(media_path_filter),
-      weak_factory_(this) {
-}
+// |NativeMediaFileUtil::Core| is used and torn-down on the media TaskRunner by
+// the owning NativeMediaFileUtil.
+class NativeMediaFileUtil::Core {
+ public:
+  explicit Core(scoped_refptr<base::SequencedTaskRunner> media_task_runner)
+      : media_task_runner_(std::move(media_task_runner)) {}
+  ~Core() = default;
+
+  // The following calls are made on the media TaskRunner, using
+  // PostTaskAndReplyWithResult() to return the result to the IO thread.
+
+  // Necessary for copy/move to succeed.
+  base::File::Error CreateDirectory(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url,
+      bool exclusive,
+      bool recursive);
+
+  base::File::Error CopyOrMoveFileLocal(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& src_url,
+      const storage::FileSystemURL& dest_url,
+      CopyOrMoveOption option,
+      bool copy);
+  base::File::Error CopyInForeignFile(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const base::FilePath& src_file_path,
+      const storage::FileSystemURL& dest_url);
+  base::File::Error DeleteFile(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url);
+
+  // Necessary for move to succeed.
+  base::File::Error DeleteDirectory(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url);
+
+  // The following calls are posted to the media TaskRunner, where they perform
+  // the specified operation, before posting |callback| back to the IO thread
+  // with the result.
+  void GetFileInfoOnTaskRunnerThread(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url,
+      GetFileInfoCallback callback);
+  void ReadDirectoryOnTaskRunnerThread(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url,
+      ReadDirectoryCallback callback);
+  void CreateSnapshotFileOnTaskRunnerThread(
+      std::unique_ptr<storage::FileSystemOperationContext> context,
+      const storage::FileSystemURL& url,
+      CreateSnapshotFileCallback callback);
+
+ private:
+  base::File::Error GetFileInfoSync(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& url,
+      base::File::Info* file_info,
+      base::FilePath* platform_path);
+  base::File::Error ReadDirectorySync(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& url,
+      EntryList* file_list);
+  base::File::Error CreateSnapshotFileSync(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& url,
+      base::File::Info* file_info,
+      base::FilePath* platform_path,
+      scoped_refptr<storage::ShareableFileReference>* file_ref);
+
+  // Translates the specified URL to a |local_file_path|, with no filtering.
+  base::File::Error GetLocalFilePath(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& file_system_url,
+      base::FilePath* local_file_path);
+
+  // Like GetLocalFilePath(), but always take media_path_filter() into
+  // consideration. If the media_path_filter() check fails, return
+  // Fila::FILE_ERROR_SECURITY. |local_file_path| does not have to exist.
+  base::File::Error GetFilteredLocalFilePath(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& file_system_url,
+      base::FilePath* local_file_path);
+
+  // Like GetLocalFilePath(), but if the file does not exist, then return
+  // |failure_error|.
+  // If |local_file_path| is a file, then take media_path_filter() into
+  // consideration.
+  // If the media_path_filter() check fails, return |failure_error|.
+  // If |local_file_path| is a directory, return File::FILE_OK.
+  base::File::Error GetFilteredLocalFilePathForExistingFileOrDirectory(
+      storage::FileSystemOperationContext* context,
+      const storage::FileSystemURL& file_system_url,
+      base::File::Error failure_error,
+      base::FilePath* local_file_path);
+
+  MediaPathFilter media_path_filter_;
+  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+NativeMediaFileUtil::NativeMediaFileUtil(
+    scoped_refptr<base::SequencedTaskRunner> media_task_runner)
+    : media_task_runner_(std::move(media_task_runner)),
+      core_(std::make_unique<Core>(media_task_runner_)) {}
 
 NativeMediaFileUtil::~NativeMediaFileUtil() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  media_task_runner_->DeleteSoon(FROM_HERE, std::move(core_));
 }
 
 // static
@@ -156,11 +260,12 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::CreateDirectoryOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
-                     exclusive, recursive, std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::CreateDirectory,
+                     base::Unretained(core_.get()), std::move(context), url,
+                     exclusive, recursive),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -173,8 +278,8 @@
   storage::FileSystemOperationContext* context_ptr = context.get();
   const bool success = context_ptr->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::GetFileInfoOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
+      base::BindOnce(&NativeMediaFileUtil::Core::GetFileInfoOnTaskRunnerThread,
+                     base::Unretained(core_.get()), std::move(context), url,
                      std::move(callback)));
   DCHECK(success);
 }
@@ -187,9 +292,10 @@
   storage::FileSystemOperationContext* context_ptr = context.get();
   const bool success = context_ptr->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::ReadDirectoryOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
-                     std::move(callback)));
+      base::BindOnce(
+          &NativeMediaFileUtil::Core::ReadDirectoryOnTaskRunnerThread,
+          base::Unretained(core_.get()), std::move(context), url,
+          std::move(callback)));
   DCHECK(success);
 }
 
@@ -221,12 +327,12 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &NativeMediaFileUtil::CopyOrMoveFileLocalOnTaskRunnerThread,
-          weak_factory_.GetWeakPtr(), std::move(context), src_url, dest_url,
-          option, true /* copy */, std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::CopyOrMoveFileLocal,
+                     base::Unretained(core_.get()), std::move(context), src_url,
+                     dest_url, option, true /* copy */),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -238,12 +344,12 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &NativeMediaFileUtil::CopyOrMoveFileLocalOnTaskRunnerThread,
-          weak_factory_.GetWeakPtr(), std::move(context), src_url, dest_url,
-          option, false /* copy */, std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::CopyOrMoveFileLocal,
+                     base::Unretained(core_.get()), std::move(context), src_url,
+                     dest_url, option, false /* copy */),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -254,11 +360,12 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::CopyInForeignFileOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context),
-                     src_file_path, dest_url, std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::CopyInForeignFile,
+                     base::Unretained(core_.get()), std::move(context),
+                     src_file_path, dest_url),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -268,11 +375,11 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::DeleteFileOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
-                     std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::DeleteFile,
+                     base::Unretained(core_.get()), std::move(context), url),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -283,11 +390,11 @@
     StatusCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   storage::FileSystemOperationContext* context_ptr = context.get();
-  const bool success = context_ptr->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::DeleteDirectoryOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
-                     std::move(callback)));
+  const bool success = base::PostTaskAndReplyWithResult(
+      context_ptr->task_runner(), FROM_HERE,
+      base::BindOnce(&NativeMediaFileUtil::Core::DeleteDirectory,
+                     base::Unretained(core_.get()), std::move(context), url),
+      std::move(callback));
   DCHECK(success);
 }
 
@@ -307,29 +414,121 @@
   storage::FileSystemOperationContext* context_ptr = context.get();
   const bool success = context_ptr->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&NativeMediaFileUtil::CreateSnapshotFileOnTaskRunnerThread,
-                     weak_factory_.GetWeakPtr(), std::move(context), url,
-                     std::move(callback)));
+      base::BindOnce(
+          &NativeMediaFileUtil::Core::CreateSnapshotFileOnTaskRunnerThread,
+          base::Unretained(core_.get()), std::move(context), url,
+          std::move(callback)));
   DCHECK(success);
 }
 
-void NativeMediaFileUtil::CreateDirectoryOnTaskRunnerThread(
+base::File::Error NativeMediaFileUtil::Core::CreateDirectory(
     std::unique_ptr<storage::FileSystemOperationContext> context,
     const storage::FileSystemURL& url,
     bool exclusive,
-    bool recursive,
-    StatusCallback callback) {
+    bool recursive) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(IsOnTaskRunnerThread(context.get()));
-  base::File::Error error =
-      CreateDirectorySync(context.get(), url, exclusive, recursive);
-  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
-                                   base::BindOnce(std::move(callback), error));
+  base::FilePath file_path;
+  base::File::Error error = GetLocalFilePath(context.get(), url, &file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  return storage::NativeFileUtil::CreateDirectory(
+      file_path, exclusive, recursive);
 }
 
-void NativeMediaFileUtil::GetFileInfoOnTaskRunnerThread(
+base::File::Error NativeMediaFileUtil::Core::CopyOrMoveFileLocal(
+    std::unique_ptr<storage::FileSystemOperationContext> context,
+    const storage::FileSystemURL& src_url,
+    const storage::FileSystemURL& dest_url,
+    CopyOrMoveOption option,
+    bool copy) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(IsOnTaskRunnerThread(context.get()));
+  base::FilePath src_file_path;
+  base::File::Error error = GetFilteredLocalFilePathForExistingFileOrDirectory(
+      context.get(), src_url, base::File::FILE_ERROR_NOT_FOUND, &src_file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  if (storage::NativeFileUtil::DirectoryExists(src_file_path))
+    return base::File::FILE_ERROR_NOT_A_FILE;
+
+  base::FilePath dest_file_path;
+  error = GetLocalFilePath(context.get(), dest_url, &dest_file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  base::File::Info file_info;
+  error = storage::NativeFileUtil::GetFileInfo(dest_file_path, &file_info);
+  if (error != base::File::FILE_OK &&
+      error != base::File::FILE_ERROR_NOT_FOUND) {
+    return error;
+  }
+  if (error == base::File::FILE_OK && file_info.is_directory)
+    return base::File::FILE_ERROR_INVALID_OPERATION;
+  if (!media_path_filter_.Match(dest_file_path))
+    return base::File::FILE_ERROR_SECURITY;
+
+  return storage::NativeFileUtil::CopyOrMoveFile(
+      src_file_path,
+      dest_file_path,
+      option,
+      storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, copy));
+}
+
+base::File::Error NativeMediaFileUtil::Core::CopyInForeignFile(
+    std::unique_ptr<storage::FileSystemOperationContext> context,
+    const base::FilePath& src_file_path,
+    const storage::FileSystemURL& dest_url) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(IsOnTaskRunnerThread(context.get()));
+  if (src_file_path.empty())
+    return base::File::FILE_ERROR_INVALID_OPERATION;
+
+  base::FilePath dest_file_path;
+  base::File::Error error =
+      GetFilteredLocalFilePath(context.get(), dest_url, &dest_file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  return storage::NativeFileUtil::CopyOrMoveFile(
+      src_file_path,
+      dest_file_path,
+      storage::FileSystemOperation::OPTION_NONE,
+      storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url,
+                                                            true /* copy */));
+}
+
+base::File::Error NativeMediaFileUtil::Core::DeleteFile(
+    std::unique_ptr<storage::FileSystemOperationContext> context,
+    const storage::FileSystemURL& url) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(IsOnTaskRunnerThread(context.get()));
+  base::File::Info file_info;
+  base::FilePath file_path;
+  base::File::Error error =
+      GetFileInfoSync(context.get(), url, &file_info, &file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  if (file_info.is_directory)
+    return base::File::FILE_ERROR_NOT_A_FILE;
+  return storage::NativeFileUtil::DeleteFile(file_path);
+}
+
+base::File::Error NativeMediaFileUtil::Core::DeleteDirectory(
+    std::unique_ptr<storage::FileSystemOperationContext> context,
+    const storage::FileSystemURL& url) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(IsOnTaskRunnerThread(context.get()));
+  base::FilePath file_path;
+  base::File::Error error = GetLocalFilePath(context.get(), url, &file_path);
+  if (error != base::File::FILE_OK)
+    return error;
+  return storage::NativeFileUtil::DeleteDirectory(file_path);
+}
+
+void NativeMediaFileUtil::Core::GetFileInfoOnTaskRunnerThread(
     std::unique_ptr<storage::FileSystemOperationContext> context,
     const storage::FileSystemURL& url,
     GetFileInfoCallback callback) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(IsOnTaskRunnerThread(context.get()));
   base::File::Info file_info;
   base::File::Error error =
@@ -339,70 +538,25 @@
       base::BindOnce(std::move(callback), error, file_info));
 }
 
-void NativeMediaFileUtil::ReadDirectoryOnTaskRunnerThread(
+void NativeMediaFileUtil::Core::ReadDirectoryOnTaskRunnerThread(
     std::unique_ptr<storage::FileSystemOperationContext> context,
     const storage::FileSystemURL& url,
     ReadDirectoryCallback callback) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(IsOnTaskRunnerThread(context.get()));
   EntryList entry_list;
-  base::File::Error error =
-      ReadDirectorySync(context.get(), url, &entry_list);
+  base::File::Error error = ReadDirectorySync(context.get(), url, &entry_list);
   content::BrowserThread::PostTask(
       content::BrowserThread::IO, FROM_HERE,
       base::BindOnce(std::move(callback), error, entry_list,
                      false /* has_more */));
 }
 
-void NativeMediaFileUtil::CopyOrMoveFileLocalOnTaskRunnerThread(
-    std::unique_ptr<storage::FileSystemOperationContext> context,
-    const storage::FileSystemURL& src_url,
-    const storage::FileSystemURL& dest_url,
-    CopyOrMoveOption option,
-    bool copy,
-    StatusCallback callback) {
-  DCHECK(IsOnTaskRunnerThread(context.get()));
-  base::File::Error error =
-      CopyOrMoveFileSync(context.get(), src_url, dest_url, option, copy);
-  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
-                                   base::BindOnce(std::move(callback), error));
-}
-
-void NativeMediaFileUtil::CopyInForeignFileOnTaskRunnerThread(
-    std::unique_ptr<storage::FileSystemOperationContext> context,
-    const base::FilePath& src_file_path,
-    const storage::FileSystemURL& dest_url,
-    StatusCallback callback) {
-  DCHECK(IsOnTaskRunnerThread(context.get()));
-  base::File::Error error =
-      CopyInForeignFileSync(context.get(), src_file_path, dest_url);
-  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
-                                   base::BindOnce(std::move(callback), error));
-}
-
-void NativeMediaFileUtil::DeleteFileOnTaskRunnerThread(
-    std::unique_ptr<storage::FileSystemOperationContext> context,
-    const storage::FileSystemURL& url,
-    StatusCallback callback) {
-  DCHECK(IsOnTaskRunnerThread(context.get()));
-  base::File::Error error = DeleteFileSync(context.get(), url);
-  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
-                                   base::BindOnce(std::move(callback), error));
-}
-
-void NativeMediaFileUtil::DeleteDirectoryOnTaskRunnerThread(
-    std::unique_ptr<storage::FileSystemOperationContext> context,
-    const storage::FileSystemURL& url,
-    StatusCallback callback) {
-  DCHECK(IsOnTaskRunnerThread(context.get()));
-  base::File::Error error = DeleteDirectorySync(context.get(), url);
-  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
-                                   base::BindOnce(std::move(callback), error));
-}
-
-void NativeMediaFileUtil::CreateSnapshotFileOnTaskRunnerThread(
+void NativeMediaFileUtil::Core::CreateSnapshotFileOnTaskRunnerThread(
     std::unique_ptr<storage::FileSystemOperationContext> context,
     const storage::FileSystemURL& url,
     CreateSnapshotFileCallback callback) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(IsOnTaskRunnerThread(context.get()));
   base::File::Info file_info;
   base::FilePath platform_path;
@@ -415,89 +569,11 @@
                      file_ref));
 }
 
-base::File::Error NativeMediaFileUtil::CreateDirectorySync(
-    storage::FileSystemOperationContext* context,
-    const storage::FileSystemURL& url,
-    bool exclusive,
-    bool recursive) {
-  base::FilePath file_path;
-  base::File::Error error = GetLocalFilePath(context, url, &file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  return storage::NativeFileUtil::CreateDirectory(
-      file_path, exclusive, recursive);
-}
-
-base::File::Error NativeMediaFileUtil::CopyOrMoveFileSync(
-    storage::FileSystemOperationContext* context,
-    const storage::FileSystemURL& src_url,
-    const storage::FileSystemURL& dest_url,
-    CopyOrMoveOption option,
-    bool copy) {
-  DCHECK(IsOnTaskRunnerThread(context));
-  base::FilePath src_file_path;
-  base::File::Error error =
-      GetFilteredLocalFilePathForExistingFileOrDirectory(
-          context, src_url,
-          base::File::FILE_ERROR_NOT_FOUND,
-          &src_file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  if (storage::NativeFileUtil::DirectoryExists(src_file_path))
-    return base::File::FILE_ERROR_NOT_A_FILE;
-
-  base::FilePath dest_file_path;
-  error = GetLocalFilePath(context, dest_url, &dest_file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  base::File::Info file_info;
-  error = storage::NativeFileUtil::GetFileInfo(dest_file_path, &file_info);
-  if (error != base::File::FILE_OK &&
-      error != base::File::FILE_ERROR_NOT_FOUND) {
-    return error;
-  }
-  if (error == base::File::FILE_OK && file_info.is_directory)
-    return base::File::FILE_ERROR_INVALID_OPERATION;
-  if (!media_path_filter_->Match(dest_file_path))
-    return base::File::FILE_ERROR_SECURITY;
-
-  return storage::NativeFileUtil::CopyOrMoveFile(
-      src_file_path,
-      dest_file_path,
-      option,
-      storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, copy));
-}
-
-base::File::Error NativeMediaFileUtil::CopyInForeignFileSync(
-    storage::FileSystemOperationContext* context,
-    const base::FilePath& src_file_path,
-    const storage::FileSystemURL& dest_url) {
-  DCHECK(IsOnTaskRunnerThread(context));
-  if (src_file_path.empty())
-    return base::File::FILE_ERROR_INVALID_OPERATION;
-
-  base::FilePath dest_file_path;
-  base::File::Error error =
-      GetFilteredLocalFilePath(context, dest_url, &dest_file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  return storage::NativeFileUtil::CopyOrMoveFile(
-      src_file_path,
-      dest_file_path,
-      storage::FileSystemOperation::OPTION_NONE,
-      storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url,
-                                                            true /* copy */));
-}
-
-base::File::Error NativeMediaFileUtil::GetFileInfoSync(
+base::File::Error NativeMediaFileUtil::Core::GetFileInfoSync(
     storage::FileSystemOperationContext* context,
     const storage::FileSystemURL& url,
     base::File::Info* file_info,
     base::FilePath* platform_path) {
-  DCHECK(context);
-  DCHECK(IsOnTaskRunnerThread(context));
-  DCHECK(file_info);
-
   base::FilePath file_path;
   base::File::Error error = GetLocalFilePath(context, url, &file_path);
   if (error != base::File::FILE_OK)
@@ -510,34 +586,16 @@
 
   if (platform_path)
     *platform_path = file_path;
-  if (file_info->is_directory ||
-      media_path_filter_->Match(file_path)) {
+  if (file_info->is_directory || media_path_filter_.Match(file_path)) {
     return base::File::FILE_OK;
   }
   return base::File::FILE_ERROR_NOT_FOUND;
 }
 
-base::File::Error NativeMediaFileUtil::GetLocalFilePath(
-    storage::FileSystemOperationContext* context,
-    const storage::FileSystemURL& url,
-    base::FilePath* local_file_path) {
-  DCHECK(local_file_path);
-  DCHECK(url.is_valid());
-  if (url.path().empty()) {
-    // Root direcory case, which should not be accessed.
-    return base::File::FILE_ERROR_ACCESS_DENIED;
-  }
-  *local_file_path = url.path();
-  return base::File::FILE_OK;
-}
-
-base::File::Error NativeMediaFileUtil::ReadDirectorySync(
+base::File::Error NativeMediaFileUtil::Core::ReadDirectorySync(
     storage::FileSystemOperationContext* context,
     const storage::FileSystemURL& url,
     EntryList* file_list) {
-  DCHECK(IsOnTaskRunnerThread(context));
-  DCHECK(file_list);
-  DCHECK(file_list->empty());
   base::File::Info file_info;
   base::FilePath dir_path;
   base::File::Error error =
@@ -565,7 +623,7 @@
     // NativeMediaFileUtil skip criteria.
     if (MediaPathFilter::ShouldSkip(enum_path))
       continue;
-    if (!info.IsDirectory() && !media_path_filter_->Match(enum_path))
+    if (!info.IsDirectory() && !media_path_filter_.Match(enum_path))
       continue;
 
     file_list->emplace_back(enum_path.BaseName(),
@@ -577,39 +635,12 @@
   return base::File::FILE_OK;
 }
 
-base::File::Error NativeMediaFileUtil::DeleteFileSync(
-    storage::FileSystemOperationContext* context,
-    const storage::FileSystemURL& url) {
-  DCHECK(IsOnTaskRunnerThread(context));
-  base::File::Info file_info;
-  base::FilePath file_path;
-  base::File::Error error =
-      GetFileInfoSync(context, url, &file_info, &file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  if (file_info.is_directory)
-    return base::File::FILE_ERROR_NOT_A_FILE;
-  return storage::NativeFileUtil::DeleteFile(file_path);
-}
-
-base::File::Error NativeMediaFileUtil::DeleteDirectorySync(
-    storage::FileSystemOperationContext* context,
-    const storage::FileSystemURL& url) {
-  DCHECK(IsOnTaskRunnerThread(context));
-  base::FilePath file_path;
-  base::File::Error error = GetLocalFilePath(context, url, &file_path);
-  if (error != base::File::FILE_OK)
-    return error;
-  return storage::NativeFileUtil::DeleteDirectory(file_path);
-}
-
-base::File::Error NativeMediaFileUtil::CreateSnapshotFileSync(
+base::File::Error NativeMediaFileUtil::Core::CreateSnapshotFileSync(
     storage::FileSystemOperationContext* context,
     const storage::FileSystemURL& url,
     base::File::Info* file_info,
     base::FilePath* platform_path,
     scoped_refptr<storage::ShareableFileReference>* file_ref) {
-  DCHECK(IsOnTaskRunnerThread(context));
   base::File::Error error =
       GetFileInfoSync(context, url, file_info, platform_path);
   if (error == base::File::FILE_OK && file_info->is_directory)
@@ -623,17 +654,29 @@
   return error;
 }
 
-base::File::Error NativeMediaFileUtil::GetFilteredLocalFilePath(
+base::File::Error NativeMediaFileUtil::Core::GetLocalFilePath(
+    storage::FileSystemOperationContext* context,
+    const storage::FileSystemURL& url,
+    base::FilePath* local_file_path) {
+  DCHECK(url.is_valid());
+  if (url.path().empty()) {
+    // Root direcory case, which should not be accessed.
+    return base::File::FILE_ERROR_ACCESS_DENIED;
+  }
+  *local_file_path = url.path();
+  return base::File::FILE_OK;
+}
+
+base::File::Error NativeMediaFileUtil::Core::GetFilteredLocalFilePath(
     storage::FileSystemOperationContext* context,
     const storage::FileSystemURL& file_system_url,
     base::FilePath* local_file_path) {
-  DCHECK(IsOnTaskRunnerThread(context));
   base::FilePath file_path;
   base::File::Error error =
       GetLocalFilePath(context, file_system_url, &file_path);
   if (error != base::File::FILE_OK)
     return error;
-  if (!media_path_filter_->Match(file_path))
+  if (!media_path_filter_.Match(file_path))
     return base::File::FILE_ERROR_SECURITY;
 
   *local_file_path = file_path;
@@ -641,12 +684,11 @@
 }
 
 base::File::Error
-NativeMediaFileUtil::GetFilteredLocalFilePathForExistingFileOrDirectory(
+NativeMediaFileUtil::Core::GetFilteredLocalFilePathForExistingFileOrDirectory(
     storage::FileSystemOperationContext* context,
     const storage::FileSystemURL& file_system_url,
     base::File::Error failure_error,
     base::FilePath* local_file_path) {
-  DCHECK(IsOnTaskRunnerThread(context));
   base::FilePath file_path;
   base::File::Error error =
       GetLocalFilePath(context, file_system_url, &file_path);
@@ -659,8 +701,7 @@
   if (!base::GetFileInfo(file_path, &file_info))
     return base::File::FILE_ERROR_FAILED;
 
-  if (!file_info.is_directory &&
-      !media_path_filter_->Match(file_path)) {
+  if (!file_info.is_directory && !media_path_filter_.Match(file_path)) {
     return failure_error;
   }
 
diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util.h b/chrome/browser/media_galleries/fileapi/native_media_file_util.h
index 47f1302..d16b90b 100644
--- a/chrome/browser/media_galleries/fileapi/native_media_file_util.h
+++ b/chrome/browser/media_galleries/fileapi/native_media_file_util.h
@@ -11,21 +11,23 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
 #include "storage/browser/fileapi/async_file_util.h"
 
 namespace net {
 class IOBuffer;
 }
 
-class MediaPathFilter;
-
-// This class handles native file system operations with media type filtering.
-// To support virtual file systems it implements the AsyncFileUtil interface
-// from scratch and provides synchronous override points.
+// Implements the AsyncFileUtil interface to perform native file system
+// operations, restricted to operating only on media files.
+// Instances must be used, and torn-down, only on the browser IO-thread.
 class NativeMediaFileUtil : public storage::AsyncFileUtil {
  public:
-  explicit NativeMediaFileUtil(MediaPathFilter* media_path_filter);
+  // |media_task_runner| specifies the TaskRunner on which to perform all
+  // native file system operations, and media file filtering. This must
+  // be the same TaskRunner passed in each FileSystemOperationContext.
+  explicit NativeMediaFileUtil(
+      scoped_refptr<base::SequencedTaskRunner> media_task_runner);
   ~NativeMediaFileUtil() override;
 
   // Uses the MIME sniffer code, which actually looks into the file,
@@ -113,122 +115,13 @@
       const storage::FileSystemURL& url,
       CreateSnapshotFileCallback callback) override;
 
- protected:
-  virtual void CreateDirectoryOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      bool exclusive,
-      bool recursive,
-      StatusCallback callback);
-  virtual void GetFileInfoOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      GetFileInfoCallback callback);
-  virtual void ReadDirectoryOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      ReadDirectoryCallback callback);
-  virtual void CopyOrMoveFileLocalOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& src_url,
-      const storage::FileSystemURL& dest_url,
-      CopyOrMoveOption option,
-      bool copy,
-      StatusCallback callback);
-  virtual void CopyInForeignFileOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const base::FilePath& src_file_path,
-      const storage::FileSystemURL& dest_url,
-      StatusCallback callback);
-  virtual void DeleteFileOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      StatusCallback callback);
-  virtual void DeleteDirectoryOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      StatusCallback callback);
-  virtual void CreateSnapshotFileOnTaskRunnerThread(
-      std::unique_ptr<storage::FileSystemOperationContext> context,
-      const storage::FileSystemURL& url,
-      CreateSnapshotFileCallback callback);
-
-  // The following methods should only be called on the task runner thread.
-
-  // Necessary for copy/move to succeed.
-  virtual base::File::Error CreateDirectorySync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url,
-      bool exclusive,
-      bool recursive);
-  virtual base::File::Error CopyOrMoveFileSync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& src_url,
-      const storage::FileSystemURL& dest_url,
-      CopyOrMoveOption option,
-      bool copy);
-  virtual base::File::Error CopyInForeignFileSync(
-      storage::FileSystemOperationContext* context,
-      const base::FilePath& src_file_path,
-      const storage::FileSystemURL& dest_url);
-  virtual base::File::Error GetFileInfoSync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url,
-      base::File::Info* file_info,
-      base::FilePath* platform_path);
-  // Called by GetFileInfoSync. Meant to be overridden by subclasses that
-  // have special mappings from URLs to platform paths (virtual filesystems).
-  virtual base::File::Error GetLocalFilePath(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& file_system_url,
-      base::FilePath* local_file_path);
-  virtual base::File::Error ReadDirectorySync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url,
-      EntryList* file_list);
-  virtual base::File::Error DeleteFileSync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url);
-  // Necessary for move to succeed.
-  virtual base::File::Error DeleteDirectorySync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url);
-  virtual base::File::Error CreateSnapshotFileSync(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& url,
-      base::File::Info* file_info,
-      base::FilePath* platform_path,
-      scoped_refptr<storage::ShareableFileReference>* file_ref);
-
-  MediaPathFilter* media_path_filter() {
-    return media_path_filter_;
-  }
-
  private:
-  // Like GetLocalFilePath(), but always take media_path_filter() into
-  // consideration. If the media_path_filter() check fails, return
-  // Fila::FILE_ERROR_SECURITY. |local_file_path| does not have to exist.
-  base::File::Error GetFilteredLocalFilePath(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& file_system_url,
-      base::FilePath* local_file_path);
+  // |core_| holds state which must be used and torn-down on the
+  // |media_task_runner_|, rather than on the IO-thread.
+  class Core;
 
-  // Like GetLocalFilePath(), but if the file does not exist, then return
-  // |failure_error|.
-  // If |local_file_path| is a file, then take media_path_filter() into
-  // consideration.
-  // If the media_path_filter() check fails, return |failure_error|.
-  // If |local_file_path| is a directory, return File::FILE_OK.
-  base::File::Error GetFilteredLocalFilePathForExistingFileOrDirectory(
-      storage::FileSystemOperationContext* context,
-      const storage::FileSystemURL& file_system_url,
-      base::File::Error failure_error,
-      base::FilePath* local_file_path);
-
-  // Not owned, owned by the backend which owns this.
-  MediaPathFilter* const media_path_filter_;
-
-  base::WeakPtrFactory<NativeMediaFileUtil> weak_factory_;
+  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
+  std::unique_ptr<Core> core_;
 
   DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtil);
 };
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 732b2b6..c561486 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -45,9 +45,6 @@
       prefs::kAcceptLanguages, profile->GetPrefs(),
       base::BindRepeating(&ProfileNetworkContextService::UpdateAcceptLanguage,
                           base::Unretained(this)));
-  // The system context must be initialized before any other network contexts.
-  // TODO(mmenke): Figure out a way to enforce this.
-  g_browser_process->system_network_context_manager()->GetContext();
   DisableQuicIfNotAllowed();
 }
 
@@ -149,7 +146,6 @@
 ProfileNetworkContextService::CreateNetworkContextParams(
     bool in_memory,
     const base::FilePath& relative_partition_path) {
-  // TODO(mmenke): Set up parameters here.
   network::mojom::NetworkContextParamsPtr network_context_params =
       CreateDefaultNetworkContextParams();
 
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index 5e74d1e..5dda0ac 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -108,13 +108,14 @@
 
   if (!network_service_network_context_ ||
       network_service_network_context_.encountered_error()) {
-    network::mojom::NetworkService* network_service =
-        content::GetNetworkService();
-    if (!is_quic_allowed_)
-      network_service->DisableQuic();
-    network_service->CreateNetworkContext(
-        MakeRequest(&network_service_network_context_),
-        CreateNetworkContextParams());
+    // This should call into OnNetworkServiceCreated(), which will re-create
+    // the network service, if needed. There's a chance that it won't be
+    // invoked, if the NetworkContext has encountered an error but the
+    // NetworkService has not yet noticed its pipe was closed. In that case,
+    // trying to create a new NetworkContext would fail, anyways, and hopefully
+    // a new NetworkContext will be created on the next GetContext() call.
+    content::GetNetworkService();
+    DCHECK(network_service_network_context_);
   }
   return network_service_network_context_.get();
 }
@@ -172,6 +173,20 @@
   shared_url_loader_factory_->Shutdown();
 }
 
+void SystemNetworkContextManager::OnNetworkServiceCreated(
+    network::mojom::NetworkService* network_service) {
+  if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
+    return;
+  // Disable QUIC globally, if needed.
+  if (!is_quic_allowed_)
+    network_service->DisableQuic();
+  // The system NetworkContext must be created first, since it sets
+  // |use_to_validate_certs| to true.
+  network_service->CreateNetworkContext(
+      MakeRequest(&network_service_network_context_),
+      CreateNetworkContextParams());
+}
+
 void SystemNetworkContextManager::DisableQuic() {
   is_quic_allowed_ = false;
 
@@ -234,6 +249,8 @@
   network_context_params->enable_ftp_url_support = true;
 #endif
 
+  network_context_params->use_to_validate_certs = true;
+
   proxy_config_monitor_.AddToNetworkContextParams(network_context_params.get());
 
   return network_context_params;
diff --git a/chrome/browser/net/system_network_context_manager.h b/chrome/browser/net/system_network_context_manager.h
index be289620..d496e268 100644
--- a/chrome/browser/net/system_network_context_manager.h
+++ b/chrome/browser/net/system_network_context_manager.h
@@ -72,6 +72,10 @@
   // that is backed by the SystemNetworkContext.
   scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory();
 
+  // Called when content creates a NetworkService. Creates the
+  // SystemNetworkContext, if the network service is enabled.
+  void OnNetworkServiceCreated(network::mojom::NetworkService* network_service);
+
   // Permanently disables QUIC, both for NetworkContexts using the IOThread's
   // NetworkService, and for those using the network service (if enabled).
   void DisableQuic();
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 7c0aecd..787aab3 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -1136,7 +1136,7 @@
   }
 #endif
 
-  if (base::FeatureList::IsEnabled(features::kMultiDeviceApi)) {
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
     service_manager::EmbeddedServiceInfo info;
     info.task_runner = base::ThreadTaskRunnerHandle::Get();
     info.factory = base::BindRepeating(&ProfileImpl::CreateDeviceSyncService,
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index fc316ed..359f6f7 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -128,6 +128,9 @@
 #include "chromeos/chromeos_switches.h"
 #include "chromeos/dbus/cryptohome_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "components/arc/arc_features.h"
+#include "components/arc/arc_prefs.h"
+#include "components/arc/arc_supervision_transition.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "components/user_manager/user_type.h"
@@ -987,15 +990,23 @@
   const user_manager::User* user =
       chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
   if (user) {
-    if (profile->IsChild() !=
-        (user->GetType() == user_manager::USER_TYPE_CHILD)) {
+    const bool user_is_child =
+        (user->GetType() == user_manager::USER_TYPE_CHILD);
+    const bool profile_is_child = profile->IsChild();
+    if (profile_is_child != user_is_child) {
       ProfileAttributesEntry* entry;
       if (storage.GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
         LOG(WARNING) << "Profile child status has changed.";
         storage.RemoveProfile(profile->GetPath());
       }
+      // Notify ARC about user type change via prefs.
+      const arc::ArcSupervisionTransition supervisionTransition =
+          user_is_child ? arc::ArcSupervisionTransition::REGULAR_TO_CHILD
+                        : arc::ArcSupervisionTransition::CHILD_TO_REGULAR;
+      profile->GetPrefs()->SetInteger(arc::prefs::kArcSupervisionTransition,
+                                      static_cast<int>(supervisionTransition));
     }
-    if (user->GetType() == user_manager::USER_TYPE_CHILD) {
+    if (user_is_child) {
       profile->GetPrefs()->SetString(prefs::kSupervisedUserId,
                                      supervised_users::kChildAccountSUID);
     } else {
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 54392da..eda15801 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -2644,10 +2644,15 @@
   if (!base::FeatureList::IsEnabled(media::kPictureInPicture))
     return;
 
-  // TODO(apacible): Re-enable contextual menu entry point for
-  // Picture-in-Picture after end to end flow through the media controls is
-  // stable. http://crbug/817613.
-  NOTIMPLEMENTED();
+  bool picture_in_picture =
+      !!(params_.media_flags & WebContextMenuData::kMediaCanPictureInPicture);
+
+  base::RecordAction(
+      UserMetricsAction("MediaContextMenu_EnterPictureInPicture"));
+  MediaPlayerActionAt(
+      gfx::Point(params_.x, params_.y),
+      WebMediaPlayerAction(WebMediaPlayerAction::kPictureInPicture,
+                           picture_in_picture));
 }
 
 void RenderViewContextMenu::WriteURLToClipboard(const GURL& url) {
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html
index 1dbb4814..19b854e 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -11,6 +11,7 @@
 <include src="saml_interstitial.html">
 <include src="throbber_notice.html">
 <include src="navigation_bar.html">
+<include src="network_select_login.html">
 <include src="unrecoverable_cryptohome_error_card.html">
 <include src="update_required_card.html">
 <include src="offline_ad_login.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chrome/browser/resources/chromeos/login/custom_elements_login.js
index 883c5be..89fa2409 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -15,6 +15,7 @@
 // <include src="saml_interstitial.js">
 // <include src="throbber_notice.js">
 // <include src="navigation_bar.js">
+// <include src="network_select_login.js">
 // <include src="unrecoverable_cryptohome_error_card.js">
 // <include src="update_required_card.js">
 // <include src="offline_ad_login.js">
diff --git a/chrome/browser/resources/chromeos/login/network_select_login.html b/chrome/browser/resources/chromeos/login/network_select_login.html
index b3c16df0..1c378cf 100644
--- a/chrome/browser/resources/chromeos/login/network_select_login.html
+++ b/chrome/browser/resources/chromeos/login/network_select_login.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_select.html">
+<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html">
 
 <dom-module id="network-select-login">
   <style>
@@ -18,7 +19,7 @@
         on-network-item-selected="onNetworkListNetworkItemSelected_"
         on-custom-item-selected="onNetworkListCustomItemSelected_"
         custom-items="[[getNetworkCustomItems_(isConnected)]]"
-        no-bottom-scroll-border
+        no-bottom-scroll-border="[[noBottomScrollBorder]]"
         class="focus-on-show">
     </cr-network-select>
   </template>
diff --git a/chrome/browser/resources/chromeos/login/network_select_login.js b/chrome/browser/resources/chromeos/login/network_select_login.js
index 04a7e06..675956f 100644
--- a/chrome/browser/resources/chromeos/login/network_select_login.js
+++ b/chrome/browser/resources/chromeos/login/network_select_login.js
@@ -38,6 +38,37 @@
     this.$.networkSelect.focus();
   },
 
+  /** Call after strings are loaded to set CrOncStrings for cr-network-select */
+  setCrOncStrings: function() {
+    CrOncStrings = {
+      OncTypeCellular: loadTimeData.getString('OncTypeCellular'),
+      OncTypeEthernet: loadTimeData.getString('OncTypeEthernet'),
+      OncTypeTether: loadTimeData.getString('OncTypeTether'),
+      OncTypeVPN: loadTimeData.getString('OncTypeVPN'),
+      OncTypeWiFi: loadTimeData.getString('OncTypeWiFi'),
+      OncTypeWiMAX: loadTimeData.getString('OncTypeWiMAX'),
+      networkListItemConnected:
+          loadTimeData.getString('networkListItemConnected'),
+      networkListItemConnecting:
+          loadTimeData.getString('networkListItemConnecting'),
+      networkListItemConnectingTo:
+          loadTimeData.getString('networkListItemConnectingTo'),
+      networkListItemInitializing:
+          loadTimeData.getString('networkListItemInitializing'),
+      networkListItemScanning:
+          loadTimeData.getString('networkListItemScanning'),
+      networkListItemNotConnected:
+          loadTimeData.getString('networkListItemNotConnected'),
+      networkListItemNoNetwork:
+          loadTimeData.getString('networkListItemNoNetwork'),
+      vpnNameTemplate: loadTimeData.getString('vpnNameTemplate'),
+
+      // Additional strings for custom items.
+      addWiFiNetworkMenuName: loadTimeData.getString('addWiFiNetworkMenuName'),
+      proxySettingsMenuName: loadTimeData.getString('proxySettingsMenuName'),
+    };
+  },
+
   /**
    * Returns custom items for network selector. Shows 'Proxy settings' only
    * when connected to a network.
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome.html b/chrome/browser/resources/chromeos/login/oobe_welcome.html
index 1463f1e..1382517 100644
--- a/chrome/browser/resources/chromeos/login/oobe_welcome.html
+++ b/chrome/browser/resources/chromeos/login/oobe_welcome.html
@@ -2,7 +2,6 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
@@ -329,7 +328,7 @@
       </div>
       <div class="footer layout vertical">
         <network-select-login id="networkSelectLogin"
-            is-connected="{{isConnected_}}">
+            is-connected="{{isConnected_}}" no-bottom-scroll-border>
         </network-select-login>
       </div>
       <div class="bottom-buttons layout horizontal justified">
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome.js b/chrome/browser/resources/chromeos/login/oobe_welcome.js
index c67b3202..434bd5d 100644
--- a/chrome/browser/resources/chromeos/login/oobe_welcome.js
+++ b/chrome/browser/resources/chromeos/login/oobe_welcome.js
@@ -103,34 +103,7 @@
    * This is called when UI strings are changed.
    */
   updateLocalizedContent: function() {
-    CrOncStrings = {
-      OncTypeCellular: loadTimeData.getString('OncTypeCellular'),
-      OncTypeEthernet: loadTimeData.getString('OncTypeEthernet'),
-      OncTypeTether: loadTimeData.getString('OncTypeTether'),
-      OncTypeVPN: loadTimeData.getString('OncTypeVPN'),
-      OncTypeWiFi: loadTimeData.getString('OncTypeWiFi'),
-      OncTypeWiMAX: loadTimeData.getString('OncTypeWiMAX'),
-      networkListItemConnected:
-          loadTimeData.getString('networkListItemConnected'),
-      networkListItemConnecting:
-          loadTimeData.getString('networkListItemConnecting'),
-      networkListItemConnectingTo:
-          loadTimeData.getString('networkListItemConnectingTo'),
-      networkListItemInitializing:
-          loadTimeData.getString('networkListItemInitializing'),
-      networkListItemScanning:
-          loadTimeData.getString('networkListItemScanning'),
-      networkListItemNotConnected:
-          loadTimeData.getString('networkListItemNotConnected'),
-      networkListItemNoNetwork:
-          loadTimeData.getString('networkListItemNoNetwork'),
-      vpnNameTemplate: loadTimeData.getString('vpnNameTemplate'),
-
-      // Additional strings for custom items.
-      addWiFiNetworkMenuName: loadTimeData.getString('addWiFiNetworkMenuName'),
-      proxySettingsMenuName: loadTimeData.getString('proxySettingsMenuName'),
-    };
-
+    this.$.networkSelectLogin.setCrOncStrings();
     this.$.welcomeScreen.i18nUpdateLocale();
     this.i18nUpdateLocale();
   },
diff --git a/chrome/browser/resources/chromeos/login/screen_error_message.css b/chrome/browser/resources/chromeos/login/screen_error_message.css
index 9d65abb2..eada576 100644
--- a/chrome/browser/resources/chromeos/login/screen_error_message.css
+++ b/chrome/browser/resources/chromeos/login/screen_error_message.css
@@ -63,12 +63,8 @@
 }
 
 #offline-network-control {
-  -webkit-align-items: center;
-  display: -webkit-flex;
-}
-
-.offline-network-list-label {
-  -webkit-margin-end: 10px;
+  height: 200px;
+  margin-bottom: 20px;
 }
 
 .button-spacer {
diff --git a/chrome/browser/resources/chromeos/login/screen_error_message.html b/chrome/browser/resources/chromeos/login/screen_error_message.html
index eb51a008..e433b60 100644
--- a/chrome/browser/resources/chromeos/login/screen_error_message.html
+++ b/chrome/browser/resources/chromeos/login/screen_error_message.html
@@ -104,23 +104,9 @@
                    show-with-ui-state-supervised
                    show-with-ui-state-kiosk-mode
                    show-with-ui-state-auto-enrollment-error">
-          <div id="offline-network-control" class="error-message-paragraph">
-            <label id="offline-networks-list-dropdown-label1"
-                i18n-content="selectNetwork"
-                class="offline-network-list-label
-                       show-with-error-state-offline
-                       show-with-error-state-proxy
-                       show-with-error-state-auth-timeout
-                       show-with-error-state-kiosk-online"></label>
-            <label id="offline-networks-list-dropdown-label2"
-                i18n-content="selectAnotherNetwork"
-                class="offline-network-list-label
-                       show-with-error-state-portal">
-            </label>
-            <div class="menu-area">
-              <div id="offline-networks-list" class="menu-control"></div>
-            </div>
-          </div>
+          <network-select-login id="offline-network-control"
+              class="layout vertical flex">
+          </network-select-login>
         </div>
         <div id="local-state-error-body"
             class="show-with-ui-state-local-state-error">
diff --git a/chrome/browser/resources/chromeos/login/screen_error_message.js b/chrome/browser/resources/chromeos/login/screen_error_message.js
index a05c446..17663f0 100644
--- a/chrome/browser/resources/chromeos/login/screen_error_message.js
+++ b/chrome/browser/resources/chromeos/login/screen_error_message.js
@@ -93,7 +93,6 @@
 
     /** @override */
     decorate: function() {
-      cr.ui.DropDown.decorate($('offline-networks-list'));
       this.updateLocalizedContent();
 
       var self = this;
@@ -245,6 +244,8 @@
       $('connecting-indicator').innerHTML =
           loadTimeData.getStringF('connectingIndicatorText', ellipsis);
 
+      $('offline-network-control').setCrOncStrings();
+
       this.onContentChange_();
     },
 
@@ -254,7 +255,6 @@
      */
     onBeforeShow: function(data) {
       cr.ui.Oobe.clearErrors();
-      cr.ui.DropDown.show('offline-networks-list', false);
       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.ERROR;
       $('error-message-back-button').disabled = !this.closable;
     },
@@ -263,7 +263,6 @@
      * Event handler that is invoked just before the screen is hidden.
      */
     onBeforeHide: function() {
-      cr.ui.DropDown.hide('offline-networks-list');
       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
     },
 
@@ -317,16 +316,6 @@
     onContentChange_: function() {
       if (Oobe.getInstance().currentScreen === this) {
         Oobe.getInstance().updateScreenSize(this);
-        if (window.getComputedStyle($('offline-networks-list-dropdown-label2'))
-                .display == 'none') {
-          $('offline-networks-list-dropdown')
-              .setAttribute(
-                  'aria-labelledby', 'offline-networks-list-dropdown-label1');
-        } else {
-          $('offline-networks-list-dropdown')
-              .setAttribute(
-                  'aria-labelledby', 'offline-networks-list-dropdown-label2');
-        }
       }
     },
 
diff --git a/chrome/browser/resources/md_bookmarks/shared_vars.html b/chrome/browser/resources/md_bookmarks/shared_vars.html
index bb2ee9a3..5926f27 100644
--- a/chrome/browser/resources/md_bookmarks/shared_vars.html
+++ b/chrome/browser/resources/md_bookmarks/shared_vars.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
+
 <style is="custom-style">
   :root {
     --card-max-width: 960px;
diff --git a/chrome/browser/resources/settings/appearance_page/home_url_input.html b/chrome/browser/resources/settings/appearance_page/home_url_input.html
index c8a457c..30ac0af 100644
--- a/chrome/browser/resources/settings/appearance_page/home_url_input.html
+++ b/chrome/browser/resources/settings/appearance_page/home_url_input.html
@@ -27,8 +27,9 @@
       }
     </style>
     <div id="outerDiv" class="settings-row">
+      <!-- Max length of 100 KB to prevent browser from freezing. -->
       <cr-input id="input" value="{{value}}" error-message="$i18n{notValid}"
-          placeholder="$i18n{enterCustomWebAddress}"
+          placeholder="$i18n{enterCustomWebAddress}" maxlength="102400"
           on-change="onChange_" on-keydown="onKeydown_" on-input="validate_"
           invalid="{{invalid}}" tab-index$="[[getTabindex_(canTab)]]"
           disabled="[[isDisabled_(disabled, pref.*)]]"
diff --git a/chrome/browser/resources/settings/people_page/manage_profile.html b/chrome/browser/resources/settings/people_page/manage_profile.html
index e547ff48..e00266b1 100644
--- a/chrome/browser/resources/settings/people_page/manage_profile.html
+++ b/chrome/browser/resources/settings/people_page/manage_profile.html
@@ -1,9 +1,9 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_profile_avatar_selector/cr_profile_avatar_selector.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
 <link rel="import" href="../i18n_setup.html">
 <link rel="import" href="manage_profile_browser_proxy.html">
@@ -18,11 +18,11 @@
       }
     </style>
     <div class="settings-box first">
-      <paper-input id="name" value="[[profileName]]" pattern=".*\S.*"
+      <cr-input id="name" value="[[profileName]]" pattern=".*\S.*"
           on-change="onProfileNameChanged_" on-keydown="onProfileNameKeydown_"
           disabled="[[isProfileNameDisabled_(syncStatus)]]" maxlength="500"
-          auto-validate no-label-float required>
-      </paper-input>
+          auto-validate required>
+      </cr-input>
     </div>
     <template is="dom-if" if="[[isProfileShortcutSettingVisible_]]">
       <div class="settings-box first">
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
index d2ec7d2..0d3fbad2 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
@@ -114,6 +114,11 @@
 // DidRedirectNavigation, and DidFinishNavigation too.
 void SafeBrowsingNavigationObserver::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
+  // Ignores navigation caused by back/forward.
+  if (navigation_handle->GetPageTransition() &
+      ui::PAGE_TRANSITION_FORWARD_BACK) {
+    return;
+  }
   std::unique_ptr<NavigationEvent> nav_event =
       std::make_unique<NavigationEvent>();
   auto it = navigation_handle_map_.find(navigation_handle);
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index c2d7774..d9d2292 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -2440,4 +2440,32 @@
                            referrer_chain.Get(3));
 }
 
+IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
+                       NavigateBackwardForward) {
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL(kSingleFrameTestURL));
+  GURL initial_url = embedded_test_server()->GetURL(kSingleFrameTestURL);
+  ClickTestLink("complete_referrer_chain", 2, initial_url);
+  auto* nav_list = navigation_event_list();
+  ASSERT_TRUE(nav_list);
+  EXPECT_EQ(3U, nav_list->Size());
+
+  // Simulates back.
+  ASSERT_TRUE(content::ExecuteScript(
+      browser()->tab_strip_model()->GetActiveWebContents(),
+      "window.history.back();"));
+  base::RunLoop().RunUntilIdle();
+
+  // Simulates forward.
+  ASSERT_TRUE(content::ExecuteScript(
+      browser()->tab_strip_model()->GetActiveWebContents(),
+      "window.history.forward();"));
+  base::RunLoop().RunUntilIdle();
+
+  nav_list = navigation_event_list();
+  ASSERT_TRUE(nav_list);
+  // Verifies navigations caused by back/forward are ignored.
+  EXPECT_EQ(3U, nav_list->Size());
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/subresource_filter/subresource_filter_abusive_unittest.cc b/chrome/browser/subresource_filter/subresource_filter_abusive_unittest.cc
index b3fb93e..5144060 100644
--- a/chrome/browser/subresource_filter/subresource_filter_abusive_unittest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_abusive_unittest.cc
@@ -72,9 +72,7 @@
         subresource_filter::Configuration::
             MakePresetForLiveRunOnPhishingSites(),
         subresource_filter::Configuration::MakePresetForLiveRunForBetterAds()};
-    scoped_configuration().ResetConfiguration(
-        base::MakeRefCounted<subresource_filter::ConfigurationList>(configs));
-    EXPECT_TRUE(base::FeatureList::IsEnabled(kAbusiveExperienceEnforce));
+    scoped_configuration().ResetConfiguration(configs);
 
     popup_blocker_ =
         SafeBrowsingTriggeredPopupBlocker::MaybeCreate(web_contents());
diff --git a/chrome/browser/tracing/background_tracing_field_trial.cc b/chrome/browser/tracing/background_tracing_field_trial.cc
index 0e8390e2..ddca303 100644
--- a/chrome/browser/tracing/background_tracing_field_trial.cc
+++ b/chrome/browser/tracing/background_tracing_field_trial.cc
@@ -103,7 +103,7 @@
 
   content::BackgroundTracingManager::GetInstance()->SetActiveScenario(
       std::move(config),
-      base::BindOnce(&BackgroundTracingUploadCallback, upload_url),
+      base::BindRepeating(&BackgroundTracingUploadCallback, upload_url),
       content::BackgroundTracingManager::ANONYMIZE_DATA);
 }
 
diff --git a/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc b/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
index c22a046..1b146c97 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
+++ b/chrome/browser/tracing/chrome_tracing_delegate_browsertest.cc
@@ -65,8 +65,8 @@
 
     DCHECK(config);
     content::BackgroundTracingManager::ReceiveCallback receive_callback =
-        base::BindOnce(&ChromeTracingDelegateBrowserTest::OnUpload,
-                       base::Unretained(this));
+        base::BindRepeating(&ChromeTracingDelegateBrowserTest::OnUpload,
+                            base::Unretained(this));
 
     return content::BackgroundTracingManager::GetInstance()->SetActiveScenario(
         std::move(config), std::move(receive_callback), data_filtering);
diff --git a/chrome/browser/ui/media_router/media_router_ui_base.cc b/chrome/browser/ui/media_router/media_router_ui_base.cc
index b7230301..4d30036 100644
--- a/chrome/browser/ui/media_router/media_router_ui_base.cc
+++ b/chrome/browser/ui/media_router/media_router_ui_base.cc
@@ -9,6 +9,7 @@
 #include <unordered_map>
 #include <utility>
 
+#include "base/atomic_sequence_num.h"
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -60,10 +61,7 @@
 }  // namespace
 
 MediaRouterUIBase::MediaRouterUIBase()
-    : current_route_request_id_(-1),
-      route_request_counter_(0),
-      initiator_(nullptr),
-      weak_factory_(this) {}
+    : initiator_(nullptr), weak_factory_(this) {}
 
 MediaRouterUIBase::~MediaRouterUIBase() {
   if (query_result_manager_.get())
@@ -194,6 +192,14 @@
              : TruncateHost(GetHostFromURL(gurl));
 }
 
+MediaRouterUIBase::RouteRequest::RouteRequest(const MediaSink::Id& sink_id)
+    : sink_id(sink_id) {
+  static base::AtomicSequenceNumber g_next_request_id;
+  id = g_next_request_id.GetNext();
+}
+
+MediaRouterUIBase::RouteRequest::~RouteRequest() = default;
+
 std::vector<MediaSource> MediaRouterUIBase::GetSourcesForCastMode(
     MediaCastMode cast_mode) const {
   return query_result_manager_->GetSourcesForCastMode(cast_mode);
@@ -242,7 +248,7 @@
     const RouteRequestResult& result) {
   DVLOG(1) << "OnRouteResponseReceived";
   // If we receive a new route that we aren't expecting, do nothing.
-  if (route_request_id != current_route_request_id_)
+  if (!current_route_request_ || route_request_id != current_route_request_->id)
     return;
 
   const MediaRoute* route = result.route();
@@ -251,7 +257,7 @@
     DVLOG(1) << "MediaRouteResponse returned error: " << result.error();
   }
 
-  current_route_request_id_ = -1;
+  current_route_request_.reset();
 }
 
 void MediaRouterUIBase::HandleCreateSessionRequestRouteResponse(
@@ -370,7 +376,7 @@
     return base::nullopt;
   }
 
-  current_route_request_id_ = ++route_request_counter_;
+  current_route_request_ = base::make_optional<RouteRequest>(sink_id);
   params.origin = for_presentation_source ? presentation_request_->frame_origin
                                           : url::Origin::Create(GURL());
   DVLOG(1) << "DoCreateRoute: origin: " << params.origin;
@@ -389,7 +395,7 @@
   if (!for_presentation_source || !start_presentation_context_) {
     params.route_response_callbacks.push_back(base::BindOnce(
         &MediaRouterUIBase::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
-        current_route_request_id_, sink_id, cast_mode,
+        current_route_request_->id, sink_id, cast_mode,
         base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
   }
   if (for_presentation_source) {
@@ -433,7 +439,7 @@
   DCHECK(!callback_.is_null());
 }
 
-MediaRouterUIBase::UIMediaRoutesObserver::~UIMediaRoutesObserver() {}
+MediaRouterUIBase::UIMediaRoutesObserver::~UIMediaRoutesObserver() = default;
 
 void MediaRouterUIBase::UIMediaRoutesObserver::OnRoutesUpdated(
     const std::vector<MediaRoute>& routes,
diff --git a/chrome/browser/ui/media_router/media_router_ui_base.h b/chrome/browser/ui/media_router/media_router_ui_base.h
index a6e17dd5..cfd5c8a 100644
--- a/chrome/browser/ui/media_router/media_router_ui_base.h
+++ b/chrome/browser/ui/media_router/media_router_ui_base.h
@@ -125,6 +125,15 @@
 #endif
 
  protected:
+  struct RouteRequest {
+   public:
+    explicit RouteRequest(const MediaSink::Id& sink_id);
+    ~RouteRequest();
+
+    int id;
+    MediaSink::Id sink_id;
+  };
+
   std::vector<MediaSource> GetSourcesForCastMode(MediaCastMode cast_mode) const;
 
   // QueryResultManager::Observer:
@@ -171,7 +180,9 @@
   // Otherwise returns an empty GURL.
   GURL GetFrameURL() const;
 
-  int current_route_request_id() const { return current_route_request_id_; }
+  const base::Optional<RouteRequest> current_route_request() const {
+    return current_route_request_;
+  }
 
   StartPresentationContext* start_presentation_context() const {
     return start_presentation_context_.get();
@@ -221,12 +232,8 @@
   // updates from them.
   std::unique_ptr<MediaRoutesObserver> routes_observer_;
 
-  // Set to -1 if not tracking a pending route request.
-  int current_route_request_id_;
-
-  // Sequential counter for route requests. Used to update
-  // |current_route_request_id_| when there is a new route request.
-  int route_request_counter_;
+  // This contains a value only when tracking a pending route request.
+  base::Optional<RouteRequest> current_route_request_;
 
   // Used for locale-aware sorting of sinks by name. Set during |InitCommon()|
   // using the current locale.
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.cc b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
index 2f4acf87..2dc599f 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
@@ -14,36 +14,6 @@
 
 namespace media_router {
 
-namespace {
-
-UIMediaSink ConvertToUISink(const MediaSinkWithCastModes& sink,
-                            const MediaRoute* route) {
-  UIMediaSink ui_sink;
-  ui_sink.id = sink.sink.id();
-  ui_sink.friendly_name = base::UTF8ToUTF16(sink.sink.name());
-  ui_sink.icon_type = sink.sink.icon_type();
-
-  if (route) {
-    ui_sink.status_text = base::UTF8ToUTF16(route->description());
-    ui_sink.route_id = route->media_route_id();
-    ui_sink.state = UIMediaSinkState::CONNECTED;
-    ui_sink.allowed_actions = static_cast<int>(UICastAction::STOP);
-  } else {
-    ui_sink.state = UIMediaSinkState::AVAILABLE;
-    if (base::ContainsKey(sink.cast_modes, PRESENTATION) ||
-        base::ContainsKey(sink.cast_modes, TAB_MIRROR)) {
-      ui_sink.allowed_actions |= static_cast<int>(UICastAction::CAST_TAB);
-    }
-    if (base::ContainsKey(sink.cast_modes, DESKTOP_MIRROR))
-      ui_sink.allowed_actions |= static_cast<int>(UICastAction::CAST_DESKTOP);
-    // TODO(takumif): Add support for local media casting.
-  }
-  DCHECK(ui_sink.allowed_actions);
-  return ui_sink;
-}
-
-}  // namespace
-
 MediaRouterViewsUI::MediaRouterViewsUI() = default;
 
 MediaRouterViewsUI::~MediaRouterViewsUI() {
@@ -64,6 +34,7 @@
 void MediaRouterViewsUI::StartCasting(const std::string& sink_id,
                                       MediaCastMode cast_mode) {
   CreateRoute(sink_id, cast_mode);
+  UpdateSinks();
 }
 
 void MediaRouterViewsUI::StopCasting(const std::string& route_id) {
@@ -106,4 +77,34 @@
     observer.OnModelUpdated(model_);
 }
 
+UIMediaSink MediaRouterViewsUI::ConvertToUISink(
+    const MediaSinkWithCastModes& sink,
+    const MediaRoute* route) {
+  UIMediaSink ui_sink;
+  ui_sink.id = sink.sink.id();
+  ui_sink.friendly_name = base::UTF8ToUTF16(sink.sink.name());
+  ui_sink.icon_type = sink.sink.icon_type();
+
+  if (route) {
+    ui_sink.status_text = base::UTF8ToUTF16(route->description());
+    ui_sink.route_id = route->media_route_id();
+    ui_sink.state = UIMediaSinkState::CONNECTED;
+    ui_sink.allowed_actions = static_cast<int>(UICastAction::STOP);
+  } else {
+    ui_sink.state = current_route_request() &&
+                            sink.sink.id() == current_route_request()->sink_id
+                        ? UIMediaSinkState::CONNECTING
+                        : UIMediaSinkState::AVAILABLE;
+    if (base::ContainsKey(sink.cast_modes, PRESENTATION) ||
+        base::ContainsKey(sink.cast_modes, TAB_MIRROR)) {
+      ui_sink.allowed_actions |= static_cast<int>(UICastAction::CAST_TAB);
+    }
+    if (base::ContainsKey(sink.cast_modes, DESKTOP_MIRROR))
+      ui_sink.allowed_actions |= static_cast<int>(UICastAction::CAST_DESKTOP);
+    // TODO(takumif): Add support for local media casting.
+  }
+  DCHECK(ui_sink.allowed_actions);
+  return ui_sink;
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.h b/chrome/browser/ui/views/media_router/media_router_views_ui.h
index eb9db95..7446d74 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.h
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.h
@@ -33,6 +33,7 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, NotifyObserver);
   FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, RemovePseudoSink);
+  FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, ConnectingState);
 
   // MediaRouterUIBase:
   void OnRoutesUpdated(
@@ -40,6 +41,9 @@
       const std::vector<MediaRoute::Id>& joinable_route_ids) override;
   void UpdateSinks() override;
 
+  UIMediaSink ConvertToUISink(const MediaSinkWithCastModes& sink,
+                              const MediaRoute* route);
+
   // Contains up-to-date data to show in the dialog.
   CastDialogModel model_;
 
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
index 50529bf..d9c0ba2 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/media_router/cast_dialog_controller.h"
 #include "chrome/browser/ui/media_router/media_cast_mode.h"
 #include "chrome/common/media_router/media_source_helper.h"
+#include "chrome/common/media_router/route_request_result.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -159,4 +160,33 @@
   ui_->RemoveObserver(&observer);
 }
 
+TEST_F(MediaRouterViewsUITest, ConnectingState) {
+  MockControllerObserver observer;
+  ui_->AddObserver(&observer);
+
+  MediaSink sink(kSinkId, kSinkName, SinkIconType::GENERIC);
+  for (MediaSinksObserver* sinks_observer : media_sinks_observers_)
+    sinks_observer->OnSinksUpdated({sink}, std::vector<url::Origin>());
+
+  // When a request to Cast to a sink is made, its state should become
+  // CONNECTING.
+  EXPECT_CALL(observer, OnModelUpdated(_))
+      .WillOnce(WithArg<0>(Invoke([&sink](const CastDialogModel& model) {
+        ASSERT_EQ(1u, model.media_sinks.size());
+        EXPECT_EQ(UIMediaSinkState::CONNECTING, model.media_sinks[0].state);
+      })));
+  ui_->StartCasting(kSinkId, MediaCastMode::TAB_MIRROR);
+
+  // Once a route is created for the sink, its state should become CONNECTED.
+  EXPECT_CALL(observer, OnModelUpdated(_))
+      .WillOnce(WithArg<0>(Invoke([&sink](const CastDialogModel& model) {
+        ASSERT_EQ(1u, model.media_sinks.size());
+        EXPECT_EQ(UIMediaSinkState::CONNECTED, model.media_sinks[0].state);
+      })));
+  MediaRoute route(kRouteId, MediaSource(kSourceId), kSinkId, "", true, true);
+  ui_->OnRoutesUpdated({route}, {});
+
+  ui_->RemoveObserver(&observer);
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 80c39d8..f541923 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -32,6 +32,7 @@
 #include "components/omnibox/browser/omnibox_edit_model.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_popup_model.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/toolbar/toolbar_model.h"
 #include "content/public/browser/web_contents.h"
@@ -48,6 +49,7 @@
 #include "ui/base/ime/text_input_client.h"
 #include "ui/base/ime/text_input_type.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/events/event.h"
@@ -132,10 +134,15 @@
       select_all_on_mouse_release_(false),
       select_all_on_gesture_tap_(false),
       latency_histogram_state_(NOT_ACTIVE),
-      friendly_suggestion_text_prefix_length_(0),
-      scoped_observer_(this) {
+      friendly_suggestion_text_prefix_length_(0) {
   set_id(VIEW_ID_OMNIBOX);
   SetFontList(font_list);
+
+  if (ui::MaterialDesignController::IsNewerMaterialUi()) {
+    InstallPlaceholderText();
+    scoped_template_url_service_observer_.Add(
+        model()->client()->GetTemplateURLService());
+  }
 }
 
 OmniboxViewViews::~OmniboxViewViews() {
@@ -215,6 +222,19 @@
   web_contents->SetUserData(OmniboxState::kKey, nullptr);
 }
 
+void OmniboxViewViews::InstallPlaceholderText() {
+  base::string16 search_provider_name = model()
+                                            ->client()
+                                            ->GetTemplateURLService()
+                                            ->GetDefaultSearchProvider()
+                                            ->short_name();
+  set_placeholder_text(l10n_util::GetStringFUTF16(IDS_OMNIBOX_PLACEHOLDER_TEXT,
+                                                  search_provider_name));
+  set_placeholder_text_color(
+      location_bar_view_->GetColor(OmniboxPart::LOCATION_BAR_TEXT_DIMMED));
+  set_placeholder_text_hidden_on_focus(true);
+}
+
 void OmniboxViewViews::EmphasizeURLComponents() {
   if (!location_bar_view_)
     return;
@@ -379,12 +399,12 @@
 
 void OmniboxViewViews::AddedToWidget() {
   views::Textfield::AddedToWidget();
-  scoped_observer_.Add(GetWidget()->GetCompositor());
+  scoped_compositor_observer_.Add(GetWidget()->GetCompositor());
 }
 
 void OmniboxViewViews::RemovedFromWidget() {
   views::Textfield::RemovedFromWidget();
-  scoped_observer_.RemoveAll();
+  scoped_compositor_observer_.RemoveAll();
 }
 
 bool OmniboxViewViews::ShouldDoLearning() {
@@ -1403,5 +1423,9 @@
 void OmniboxViewViews::OnCompositingChildResizing(ui::Compositor* compositor) {}
 
 void OmniboxViewViews::OnCompositingShuttingDown(ui::Compositor* compositor) {
-  scoped_observer_.RemoveAll();
+  scoped_compositor_observer_.RemoveAll();
+}
+
+void OmniboxViewViews::OnTemplateURLServiceChanged() {
+  InstallPlaceholderText();
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
index dec4197..5d151e9 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -16,6 +16,7 @@
 #include "base/scoped_observer.h"
 #include "build/build_config.h"
 #include "components/omnibox/browser/omnibox_view.h"
+#include "components/search_engines/template_url_service_observer.h"
 #include "components/security_state/core/security_state.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/compositor/compositor.h"
@@ -52,7 +53,8 @@
                              CandidateWindowObserver,
 #endif
                          public views::TextfieldController,
-                         public ui::CompositorObserver {
+                         public ui::CompositorObserver,
+                         public TemplateURLServiceObserver {
  public:
   // The internal view class name.
   static const char kViewClassName[];
@@ -84,6 +86,11 @@
   // Called to clear the saved state for |web_contents|.
   void ResetTabState(content::WebContents* web_contents);
 
+  // Installs the placeholder text with the name of the current default search
+  // provider. For example, if Google is the default search provider, this shows
+  // "Search Google or type a URL" when the Omnibox is empty and unfocused.
+  void InstallPlaceholderText();
+
   // OmniboxView:
   void EmphasizeURLComponents() override;
   void Update() override;
@@ -257,6 +264,9 @@
   void OnCompositingChildResizing(ui::Compositor* compositor) override;
   void OnCompositingShuttingDown(ui::Compositor* compositor) override;
 
+  // TemplateURLServiceObserver:
+  void OnTemplateURLServiceChanged() override;
+
   // When true, the location bar view is read only and also is has a slightly
   // different presentation (smaller font size). This is used for popups.
   bool popup_window_mode_;
@@ -323,7 +333,10 @@
   // this is set to 7 (the length of "Google ").
   int friendly_suggestion_text_prefix_length_;
 
-  ScopedObserver<ui::Compositor, ui::CompositorObserver> scoped_observer_;
+  ScopedObserver<ui::Compositor, ui::CompositorObserver>
+      scoped_compositor_observer_{this};
+  ScopedObserver<TemplateURLService, TemplateURLServiceObserver>
+      scoped_template_url_service_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(OmniboxViewViews);
 };
diff --git a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
index ffa5c4b..c06d4e4 100644
--- a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
@@ -6,6 +6,7 @@
 
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/login/screens/error_screen.h"
+#include "chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/login/localized_values_builder.h"
@@ -104,6 +105,9 @@
   builder->Add("configureCertsButton", IDS_MANAGE_CERTIFICATES);
   builder->Add("continueButton", IDS_NETWORK_SELECTION_CONTINUE_BUTTON);
   builder->Add("okButton", IDS_APP_OK);
+  builder->Add("proxySettingsMenuName", IDS_PROXY_SETTINGS_MENU_NAME);
+  builder->Add("addWiFiNetworkMenuName", IDS_ADD_WI_FI_NETWORK_MENU_NAME);
+  network_element::AddLocalizedValuesToBuilder(builder);
 }
 
 void ErrorScreenHandler::Initialize() {
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui.cc b/chrome/browser/ui/webui/media_router/media_router_ui.cc
index 95425a5..2155164 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_ui.cc
@@ -620,9 +620,10 @@
   // essentially mirroring.
   params.origin = url::Origin::Create(GURL(chrome::kChromeUIMediaRouterURL));
 
+  int request_id = current_route_request() ? current_route_request()->id : -1;
   params.route_response_callbacks.push_back(base::BindOnce(
       &MediaRouterUI::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
-      current_route_request_id(), sink_id, MediaCastMode::LOCAL_FILE,
+      request_id, sink_id, MediaCastMode::LOCAL_FILE,
       base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
 
   params.route_response_callbacks.push_back(
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui.h b/chrome/browser/ui/webui/media_router/media_router_ui.h
index 5c75e2d..92b5c74 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui.h
+++ b/chrome/browser/ui/webui/media_router/media_router_ui.h
@@ -80,7 +80,7 @@
   // Returns the hostname of the PresentationRequest's parent frame URL.
   virtual std::string GetPresentationRequestSourceName() const;
   bool HasPendingRouteRequest() const {
-    return current_route_request_id() != -1;
+    return current_route_request().has_value();
   }
   const std::vector<MediaRoute::Id>& joinable_route_ids() const {
     return joinable_route_ids_;
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index f58ba158..56fb94d 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -384,9 +384,6 @@
 // Enables or disables multidevice features and corresponding UI on Chrome OS.
 const base::Feature kMultidevice{"Multidevice",
                                  base::FEATURE_DISABLED_BY_DEFAULT};
-// Enables or disables the MultiDevice API on Chrome OS.
-const base::Feature kMultiDeviceApi{"MultiDeviceApi",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
 // Enables the use of native notification centers instead of using the Message
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 99f9196..804d071a 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -214,7 +214,6 @@
 
 #if defined(OS_CHROMEOS)
 extern const base::Feature kMultidevice;
-extern const base::Feature kMultiDeviceApi;
 #endif
 
 #if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
index 2db8199..0d63986 100644
--- a/chrome/common/extensions/docs/server2/app.yaml
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -1,5 +1,5 @@
 application: chrome-apps-doc
-version: 3-55-0
+version: 3-56-0
 runtime: python27
 api_version: 1
 threadsafe: false
diff --git a/chrome/installer/mac/app/Info.plist b/chrome/installer/mac/app/Info.plist
index ef2f4fc..f854a4ff 100644
--- a/chrome/installer/mac/app/Info.plist
+++ b/chrome/installer/mac/app/Info.plist
@@ -23,7 +23,7 @@
   <key>CFBundleVersion</key>
   <string>1</string>
   <key>LSMinimumSystemVersion</key>
-  <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+  <string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
   <key>NSHumanReadableCopyright</key>
   <string>Copyright © 2016 Google. All rights reserved.</string>
   <key>NSMainNibFile</key>
diff --git a/chrome/notification_helper/notification_activator.cc b/chrome/notification_helper/notification_activator.cc
index 3e65264..a979320 100644
--- a/chrome/notification_helper/notification_activator.cc
+++ b/chrome/notification_helper/notification_activator.cc
@@ -22,18 +22,36 @@
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
-enum class NotificationActivatorStatus {
+enum class NotificationActivatorPrimaryStatus {
   kSuccess = 0,
   kChromeExeMissing = 1,
-  kLaunchChromeFailed = 2,
-  kLaunchIdEmpty = 3,
-  kAllowSetForegroundWindowFailed = 4,
-  kMaxValue = kAllowSetForegroundWindowFailed,
+  kShellExecuteFailed = 2,
+  kMaxValue = kShellExecuteFailed,
 };
 
-void LogNotificationActivatorHistogram(NotificationActivatorStatus status) {
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class NotificationActivatorSecondaryStatus {
+  kSuccess = 0,
+  kLaunchIdEmpty = 1 << 0,
+  kAllowSetForegroundWindowFailed = 1 << 1,
+  kProcessHandleMissing = 1 << 2,
+  kScenarioCount = 1 << 3,
+  kMaxValue = kScenarioCount,
+};
+
+void LogNotificationActivatorPrimaryStatus(
+    NotificationActivatorPrimaryStatus status) {
   UMA_HISTOGRAM_ENUMERATION(
-      "Notifications.NotificationHelper.NotificationActivatorStatus", status);
+      "Notifications.NotificationHelper.NotificationActivatorPrimaryStatus",
+      status);
+}
+
+void LogNotificationActivatorSecondaryStatus(
+    NotificationActivatorSecondaryStatus status) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "Notifications.NotificationHelper.NotificationActivatorSecondaryStatus",
+      status);
 }
 
 }  // namespace
@@ -70,12 +88,13 @@
   base::FilePath chrome_exe_path = GetChromeExePath();
   if (chrome_exe_path.empty()) {
     Trace(L"Failed to get chrome exe path\n");
-    LogNotificationActivatorHistogram(
-        NotificationActivatorStatus::kChromeExeMissing);
+    LogNotificationActivatorPrimaryStatus(
+        NotificationActivatorPrimaryStatus::kChromeExeMissing);
     return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
   }
 
-  bool is_hitogram_logged = false;
+  int secondary_status =
+      static_cast<int>(NotificationActivatorSecondaryStatus::kSuccess);
 
   // |invoked_args| contains the launch ID string encoded by Chrome. Chrome adds
   // it to the launch argument of the toast and gets it back via |invoked_args|
@@ -86,9 +105,8 @@
   // launch chrome. However, we still launch chrome for now to help investigate
   // issue 839942.
   if (invoked_args == nullptr || invoked_args[0] == 0) {
-    LogNotificationActivatorHistogram(
-        NotificationActivatorStatus::kLaunchIdEmpty);
-    is_hitogram_logged = true;
+    secondary_status |=
+        static_cast<int>(NotificationActivatorSecondaryStatus::kLaunchIdEmpty);
   }
   base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
   command_line.AppendSwitchNative(switches::kNotificationLaunchId,
@@ -117,8 +135,8 @@
   if (!::ShellExecuteEx(&info)) {
     DWORD error_code = ::GetLastError();
     Trace(L"Unable to launch Chrome.exe; error: 0x%08X\n", error_code);
-    LogNotificationActivatorHistogram(
-        NotificationActivatorStatus::kLaunchChromeFailed);
+    LogNotificationActivatorPrimaryStatus(
+        NotificationActivatorPrimaryStatus::kShellExecuteFailed);
     return HRESULT_FROM_WIN32(error_code);
   }
 
@@ -134,14 +152,20 @@
       // The lack of ability to set the window to foreground is not reason
       // enough to fail the activation call. The user will see the Chrome icon
       // flash in the task bar if this happens, which is a graceful failure.
-      LogNotificationActivatorHistogram(
-          NotificationActivatorStatus::kAllowSetForegroundWindowFailed);
-      is_hitogram_logged = true;
+      secondary_status |=
+          static_cast<int>(NotificationActivatorSecondaryStatus::
+                               kAllowSetForegroundWindowFailed);
     }
+  } else {
+    secondary_status |= static_cast<int>(
+        NotificationActivatorSecondaryStatus::kProcessHandleMissing);
   }
 
-  if (!is_hitogram_logged)
-    LogNotificationActivatorHistogram(NotificationActivatorStatus::kSuccess);
+  LogNotificationActivatorPrimaryStatus(
+      NotificationActivatorPrimaryStatus::kSuccess);
+
+  LogNotificationActivatorSecondaryStatus(
+      static_cast<NotificationActivatorSecondaryStatus>(secondary_status));
 
   return S_OK;
 }
diff --git a/chrome/test/data/chromeos/oobe_webui_browsertest.js b/chrome/test/data/chromeos/oobe_webui_browsertest.js
index 23e6d92..3fa5044f 100644
--- a/chrome/test/data/chromeos/oobe_webui_browsertest.js
+++ b/chrome/test/data/chromeos/oobe_webui_browsertest.js
@@ -66,7 +66,6 @@
 
     var requiredOwnedAriaRoleMissingSelectors = [
       '#networks-list-dropdown-container',
-      '#offline-networks-list-dropdown-container',
       '#supervised-user-creation-image-grid',
       'body > .decorated',
     ];
diff --git a/chrome/test/data/webui/cr_elements/cr_input_test.js b/chrome/test/data/webui/cr_elements/cr_input_test.js
index c0634e3b..a647d2cd0 100644
--- a/chrome/test/data/webui/cr_elements/cr_input_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_input_test.js
@@ -40,6 +40,10 @@
     assertEquals('text', input.type);
     crInput.setAttribute('type', 'password');
     assertEquals('password', input.type);
+
+    assertEquals(-1, input.maxLength);
+    crInput.setAttribute('maxlength', 5);
+    assertEquals(5, input.maxLength);
   });
 
   test('placeholderCorrectlyBound', function() {
@@ -112,4 +116,40 @@
       assertTrue(0 != underline.offsetWidth);
     });
   });
+
+  test('validation', function() {
+    crInput.value = 'FOO';
+    // Note that even with |autoValidate|, crInput.invalid only updates after
+    // |value| is changed.
+    crInput.autoValidate = true;
+    assertFalse(crInput.hasAttribute('required'));
+    assertFalse(crInput.invalid);
+
+    crInput.setAttribute('required', 'required');
+    assertTrue(input.required);
+    assertFalse(crInput.invalid);
+
+    crInput.value = '';
+    assertTrue(crInput.invalid);
+    crInput.value = 'BAR';
+    assertFalse(crInput.invalid);
+
+    const testPattern = '[a-z]+';
+    crInput.setAttribute('pattern', testPattern);
+    assertEquals(testPattern, input.pattern);
+    crInput.value = 'FOO';
+    assertTrue(crInput.invalid);
+    crInput.value = 'foo';
+    assertFalse(crInput.invalid);
+
+    // Without |autoValidate|, crInput.invalid should not change even if input
+    // value is not valid.
+    crInput.autoValidate = false;
+    crInput.value = 'ALL CAPS';
+    assertFalse(crInput.invalid);
+    assertFalse(input.checkValidity());
+    crInput.value = '';
+    assertFalse(crInput.invalid);
+    assertFalse(input.checkValidity());
+  });
 });
diff --git a/chromecast/media/cma/backend/mixer_input.cc b/chromecast/media/cma/backend/mixer_input.cc
index 8028c6ca..4d3eac8 100644
--- a/chromecast/media/cma/backend/mixer_input.cc
+++ b/chromecast/media/cma/backend/mixer_input.cc
@@ -42,7 +42,6 @@
       primary_(source->primary()),
       device_id_(source->device_id()),
       content_type_(source->content_type()),
-      output_samples_per_second_(output_samples_per_second),
       filter_group_(filter_group),
       stream_volume_multiplier_(1.0f),
       type_volume_multiplier_(1.0f),
@@ -52,10 +51,10 @@
   DCHECK(source_);
   DCHECK_GT(num_channels_, 0);
   DCHECK_GT(input_samples_per_second_, 0);
-  DCHECK_GT(output_samples_per_second_, 0);
 
   int source_read_size = read_size;
-  if (output_samples_per_second != input_samples_per_second_) {
+  if (output_samples_per_second > 0 &&
+      output_samples_per_second != input_samples_per_second_) {
     // Round up to nearest multiple of SincResampler::kKernelSize. The read size
     // must be > kKernelSize, so we round up to at least 2 * kKernelSize.
     source_read_size = std::max(source_->desired_read_size(),
diff --git a/chromecast/media/cma/backend/mixer_input.h b/chromecast/media/cma/backend/mixer_input.h
index 46d1421a..c054e7f 100644
--- a/chromecast/media/cma/backend/mixer_input.h
+++ b/chromecast/media/cma/backend/mixer_input.h
@@ -143,7 +143,6 @@
   const bool primary_;
   const std::string device_id_;
   const AudioContentType content_type_;
-  const int output_samples_per_second_;
 
   FilterGroup* filter_group_;
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 7cdf886..2f44caa 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-10741.0.0
\ No newline at end of file
+10747.0.0
\ No newline at end of file
diff --git a/chromeos/chromeos_features.cc b/chromeos/chromeos_features.cc
index 3fe1528..59b9537 100644
--- a/chromeos/chromeos_features.cc
+++ b/chromeos/chromeos_features.cc
@@ -17,6 +17,10 @@
 const base::Feature kEnableUnifiedMultiDeviceSetup{
     "EnableUnifiedMultiDeviceSetup", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables or disables the MultiDevice API on Chrome OS.
+const base::Feature kMultiDeviceApi{"MultiDeviceApi",
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 
 }  // namespace chromeos
diff --git a/chromeos/chromeos_features.h b/chromeos/chromeos_features.h
index 5d41251..60a328d 100644
--- a/chromeos/chromeos_features.h
+++ b/chromeos/chromeos_features.h
@@ -18,6 +18,8 @@
 CHROMEOS_EXPORT extern const base::Feature kEnableUnifiedMultiDeviceSettings;
 CHROMEOS_EXPORT extern const base::Feature kEnableUnifiedMultiDeviceSetup;
 
+CHROMEOS_EXPORT extern const base::Feature kMultiDeviceApi;
+
 }  // namespace features
 
 }  // namespace chromeos
diff --git a/chromeos/components/tether/BUILD.gn b/chromeos/components/tether/BUILD.gn
index f2fd458..3f1ba0e 100644
--- a/chromeos/components/tether/BUILD.gn
+++ b/chromeos/components/tether/BUILD.gn
@@ -10,10 +10,6 @@
     "active_host.h",
     "active_host_network_state_updater.cc",
     "active_host_network_state_updater.h",
-    "ad_hoc_ble_advertiser.cc",
-    "ad_hoc_ble_advertiser.h",
-    "ad_hoc_ble_advertiser_impl.cc",
-    "ad_hoc_ble_advertiser_impl.h",
     "asynchronous_shutdown_object_container.h",
     "asynchronous_shutdown_object_container_impl.cc",
     "asynchronous_shutdown_object_container_impl.h",
@@ -42,10 +38,6 @@
     "connection_preserver.h",
     "connection_preserver_impl.cc",
     "connection_preserver_impl.h",
-    "connection_priority.cc",
-    "connection_priority.h",
-    "connection_reason.cc",
-    "connection_reason.h",
     "crash_recovery_manager.h",
     "crash_recovery_manager_impl.cc",
     "crash_recovery_manager_impl.h",
@@ -150,6 +142,8 @@
     "//chromeos",
     "//chromeos/components/proximity_auth/logging",
     "//chromeos/components/tether/proto",
+    "//chromeos/services/device_sync/public/cpp",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//components/cryptauth",
     "//components/cryptauth/ble",
     "//components/pref_registry",
@@ -172,8 +166,6 @@
   sources = [
     "fake_active_host.cc",
     "fake_active_host.h",
-    "fake_ad_hoc_ble_advertiser.cc",
-    "fake_ad_hoc_ble_advertiser.h",
     "fake_asynchronous_shutdown_object_container.cc",
     "fake_asynchronous_shutdown_object_container.h",
     "fake_ble_advertiser.cc",
@@ -238,6 +230,8 @@
     "//base",
     "//chromeos",
     "//chromeos/components/tether/proto",
+    "//chromeos/services/device_sync/public/cpp:test_support",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//components/cryptauth",
     "//components/cryptauth:test_support",
     "//components/cryptauth/ble:test_support",
@@ -254,7 +248,6 @@
   sources = [
     "active_host_network_state_updater_unittest.cc",
     "active_host_unittest.cc",
-    "ad_hoc_ble_advertiser_impl_unittest.cc",
     "asynchronous_shutdown_object_container_impl_unittest.cc",
     "ble_advertisement_device_queue_unittest.cc",
     "ble_advertiser_impl_unittest.cc",
@@ -306,6 +299,8 @@
     "//chromeos",
     "//chromeos:test_support",
     "//chromeos/components/tether/proto",
+    "//chromeos/services/device_sync/public/cpp:test_support",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//components/cryptauth",
     "//components/cryptauth:test_support",
     "//components/cryptauth/ble",
diff --git a/chromeos/components/tether/DEPS b/chromeos/components/tether/DEPS
index 1ea0471..e091673 100644
--- a/chromeos/components/tether/DEPS
+++ b/chromeos/components/tether/DEPS
@@ -1,4 +1,8 @@
 include_rules = [
+  # TODO(hansberry): Restrict //chromeos/services/secure_channel dependency
+  # to //chromeos/services/secure_channel/public once SecureChannelClient
+  # migration is complete.
+  "+chromeos/services/secure_channel",
   "+components/cryptauth",
   "+components/pref_registry",
   "+components/proximity_auth/logging",
diff --git a/chromeos/components/tether/ad_hoc_ble_advertiser.cc b/chromeos/components/tether/ad_hoc_ble_advertiser.cc
deleted file mode 100644
index 79d72b6..0000000
--- a/chromeos/components/tether/ad_hoc_ble_advertiser.cc
+++ /dev/null
@@ -1,30 +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.
-
-#include "chromeos/components/tether/ad_hoc_ble_advertiser.h"
-
-namespace chromeos {
-
-namespace tether {
-
-AdHocBleAdvertiser::AdHocBleAdvertiser() {}
-
-AdHocBleAdvertiser::~AdHocBleAdvertiser() {}
-
-void AdHocBleAdvertiser::AddObserver(Observer* observer) {
-  observer_list_.AddObserver(observer);
-}
-
-void AdHocBleAdvertiser::RemoveObserver(Observer* observer) {
-  observer_list_.RemoveObserver(observer);
-}
-
-void AdHocBleAdvertiser::NotifyAsynchronousShutdownComplete() {
-  for (auto& observer : observer_list_)
-    observer.OnAsynchronousShutdownComplete();
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/ad_hoc_ble_advertiser.h b/chromeos/components/tether/ad_hoc_ble_advertiser.h
deleted file mode 100644
index 28c42e1..0000000
--- a/chromeos/components/tether/ad_hoc_ble_advertiser.h
+++ /dev/null
@@ -1,63 +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.
-
-#ifndef CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_H_
-#define CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_H_
-
-#include "base/macros.h"
-#include "base/observer_list.h"
-
-namespace chromeos {
-
-namespace tether {
-
-// Works around crbug.com/784968. This bug causes the tether host's GATT server
-// to shut down incorrectly, leaving behind a "stale" advertisement which does
-// not have any registered GATT services. This prevents a BLE connection from
-// being created. When this situation occurs, AdHocBleAdvertiser can work
-// around the issue by advertising to the device again.
-//
-// This class is *different* from BleAdvertiser because it advertises for an
-// extended period of time, regardless of whether the device to which it is
-// advertising is part of the BleAdvertisementDeviceQueue. The normal flow
-// (i.e., BleConnectionManager and BleAdvertiser) stops advertising to a device
-// once an advertisement is received from that same device; instead, this class
-// keeps advertising regardless. This ensures that the remote device receives
-// the advertisement.
-class AdHocBleAdvertiser {
- public:
-  class Observer {
-   public:
-    virtual void OnAsynchronousShutdownComplete() = 0;
-    virtual ~Observer() {}
-  };
-
-  AdHocBleAdvertiser();
-  virtual ~AdHocBleAdvertiser();
-
-  // Requests that |remote_device| add GATT services. This should only be called
-  // when the device has a stale advertisement with no GATT services. See
-  // crbug.com/784968.
-  virtual void RequestGattServicesForDevice(const std::string& device_id) = 0;
-
-  // Returns whether there are any pending requests for GATT services.
-  virtual bool HasPendingRequests() = 0;
-
-  void AddObserver(Observer* observer);
-  void RemoveObserver(Observer* observer);
-
- protected:
-  void NotifyAsynchronousShutdownComplete();
-
- private:
-  base::ObserverList<Observer> observer_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(AdHocBleAdvertiser);
-};
-
-}  // namespace tether
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_H_
diff --git a/chromeos/components/tether/ad_hoc_ble_advertiser_impl.cc b/chromeos/components/tether/ad_hoc_ble_advertiser_impl.cc
deleted file mode 100644
index c87e580..0000000
--- a/chromeos/components/tether/ad_hoc_ble_advertiser_impl.cc
+++ /dev/null
@@ -1,136 +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.
-
-#include "chromeos/components/tether/ad_hoc_ble_advertiser_impl.h"
-
-#include "base/bind.h"
-#include "chromeos/components/proximity_auth/logging/logging.h"
-#include "chromeos/components/tether/error_tolerant_ble_advertisement_impl.h"
-#include "chromeos/components/tether/timer_factory.h"
-#include "components/cryptauth/ble/ble_advertisement_generator.h"
-#include "components/cryptauth/remote_device_ref.h"
-
-namespace chromeos {
-
-namespace tether {
-
-namespace {
-
-// Empirically, phones generally pick up scan results in 1-6 seconds. 12 seconds
-// adds an extra buffer to that time to ensure that the advertisement is
-// discovered by the Tether host.
-constexpr const int64_t kNumSecondsToAdvertise = 12;
-
-}  // namespace
-
-AdHocBleAdvertiserImpl::AdvertisementWithTimer::AdvertisementWithTimer(
-    std::unique_ptr<ErrorTolerantBleAdvertisement> advertisement,
-    std::unique_ptr<base::Timer> timer)
-    : advertisement(std::move(advertisement)), timer(std::move(timer)) {}
-
-AdHocBleAdvertiserImpl::AdvertisementWithTimer::~AdvertisementWithTimer() {}
-
-AdHocBleAdvertiserImpl::AdHocBleAdvertiserImpl(
-    cryptauth::LocalDeviceDataProvider* local_device_data_provider,
-    cryptauth::RemoteBeaconSeedFetcher* remote_beacon_seed_fetcher,
-    BleSynchronizerBase* ble_synchronizer)
-    : local_device_data_provider_(local_device_data_provider),
-      remote_beacon_seed_fetcher_(remote_beacon_seed_fetcher),
-      ble_synchronizer_(ble_synchronizer),
-      timer_factory_(std::make_unique<TimerFactory>()),
-      weak_ptr_factory_(this) {}
-
-AdHocBleAdvertiserImpl::~AdHocBleAdvertiserImpl() {}
-
-void AdHocBleAdvertiserImpl::RequestGattServicesForDevice(
-    const std::string& device_id) {
-  // If an advertisement to that device is already in progress, there is nothing
-  // to do.
-  if (device_id_to_advertisement_with_timer_map_.find(device_id) !=
-      device_id_to_advertisement_with_timer_map_.end()) {
-    PA_LOG(INFO) << "Advertisement already in progress to device with ID \""
-                 << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
-                        device_id)
-                 << "\".";
-    return;
-  }
-
-  // Generate a new advertisement to device with ID |device_id|.
-  std::unique_ptr<cryptauth::DataWithTimestamp> service_data =
-      cryptauth::BleAdvertisementGenerator::GenerateBleAdvertisement(
-          device_id, local_device_data_provider_, remote_beacon_seed_fetcher_);
-  if (!service_data) {
-    PA_LOG(WARNING) << "Cannot generate advertisement for device with ID \""
-                    << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
-                           device_id)
-                    << "\"; GATT services cannot be requested.";
-    return;
-  }
-
-  std::unique_ptr<ErrorTolerantBleAdvertisement> advertisement =
-      ErrorTolerantBleAdvertisementImpl::Factory::NewInstance(
-          device_id, std::move(service_data), ble_synchronizer_);
-
-  PA_LOG(INFO) << "Requesting GATT services for device with ID \""
-               << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
-               << "\" by creating a new advertisement to that device.";
-
-  std::unique_ptr<base::Timer> timer = timer_factory_->CreateOneShotTimer();
-  timer->Start(FROM_HERE, base::TimeDelta::FromSeconds(kNumSecondsToAdvertise),
-               base::Bind(&AdHocBleAdvertiserImpl::OnTimerFired,
-                          weak_ptr_factory_.GetWeakPtr(), device_id));
-
-  device_id_to_advertisement_with_timer_map_.emplace(
-      std::piecewise_construct, std::forward_as_tuple(device_id),
-      std::forward_as_tuple(std::move(advertisement), std::move(timer)));
-}
-
-bool AdHocBleAdvertiserImpl::HasPendingRequests() {
-  return !device_id_to_advertisement_with_timer_map_.empty();
-}
-
-void AdHocBleAdvertiserImpl::OnTimerFired(const std::string& device_id) {
-  auto it = device_id_to_advertisement_with_timer_map_.find(device_id);
-  if (it == device_id_to_advertisement_with_timer_map_.end()) {
-    PA_LOG(ERROR) << "Timer fired for device with ID \""
-                  << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
-                         device_id)
-                  << "\", but that device is not present in the map.";
-    return;
-  }
-
-  PA_LOG(INFO) << "Stopping workaround advertisement for device with ID \""
-               << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
-               << "\".";
-
-  it->second.advertisement->Stop(
-      base::Bind(&AdHocBleAdvertiserImpl::OnAdvertisementStopped,
-                 weak_ptr_factory_.GetWeakPtr(), device_id));
-}
-
-void AdHocBleAdvertiserImpl::OnAdvertisementStopped(
-    const std::string& device_id) {
-  auto it = device_id_to_advertisement_with_timer_map_.find(device_id);
-  if (it == device_id_to_advertisement_with_timer_map_.end()) {
-    PA_LOG(ERROR) << "Advertisement stopped for device ID \""
-                  << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
-                         device_id)
-                  << "\", but that device is not present in the map.";
-    return;
-  }
-
-  device_id_to_advertisement_with_timer_map_.erase(it);
-
-  if (device_id_to_advertisement_with_timer_map_.empty())
-    NotifyAsynchronousShutdownComplete();
-}
-
-void AdHocBleAdvertiserImpl::SetTimerFactoryForTesting(
-    std::unique_ptr<TimerFactory> test_timer_factory) {
-  timer_factory_ = std::move(test_timer_factory);
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/ad_hoc_ble_advertiser_impl.h b/chromeos/components/tether/ad_hoc_ble_advertiser_impl.h
deleted file mode 100644
index 3e2c174..0000000
--- a/chromeos/components/tether/ad_hoc_ble_advertiser_impl.h
+++ /dev/null
@@ -1,85 +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.
-
-#ifndef CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_IMPL_H_
-#define CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_IMPL_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "chromeos/components/tether/ad_hoc_ble_advertiser.h"
-
-namespace base {
-class Timer;
-}  // namespace base
-
-namespace cryptauth {
-class LocalDeviceDataProvider;
-class RemoteBeaconSeedFetcher;
-}  // namespace cryptauth
-
-namespace chromeos {
-
-namespace tether {
-
-class BleSynchronizerBase;
-class ErrorTolerantBleAdvertisement;
-class TimerFactory;
-
-// Concrete AdHocBleAdvertiser implementation. To work around the GATT
-// services bug, this class advertises to the device whose GATT services are
-// unavailable. When the remote device receives this advertisement, it is
-// expected to re-add these GATT services. See crbug.com/784968.
-class AdHocBleAdvertiserImpl : public AdHocBleAdvertiser {
- public:
-  AdHocBleAdvertiserImpl(
-      cryptauth::LocalDeviceDataProvider* local_device_data_provider,
-      cryptauth::RemoteBeaconSeedFetcher* remote_beacon_seed_fetcher,
-      BleSynchronizerBase* ble_synchronizer);
-  ~AdHocBleAdvertiserImpl() override;
-
-  // AdHocBleAdvertiser:
-  void RequestGattServicesForDevice(const std::string& device_id) override;
-  bool HasPendingRequests() override;
-
- private:
-  friend class AdHocBleAdvertiserImplTest;
-
-  struct AdvertisementWithTimer {
-    AdvertisementWithTimer(
-        std::unique_ptr<ErrorTolerantBleAdvertisement> advertisement,
-        std::unique_ptr<base::Timer> timer);
-    ~AdvertisementWithTimer();
-
-    std::unique_ptr<ErrorTolerantBleAdvertisement> advertisement;
-    std::unique_ptr<base::Timer> timer;
-  };
-
-  void OnTimerFired(const std::string& device_id);
-  void OnAdvertisementStopped(const std::string& device_id);
-
-  void SetTimerFactoryForTesting(
-      std::unique_ptr<TimerFactory> test_timer_factory);
-
-  cryptauth::LocalDeviceDataProvider* local_device_data_provider_;
-  cryptauth::RemoteBeaconSeedFetcher* remote_beacon_seed_fetcher_;
-  BleSynchronizerBase* ble_synchronizer_;
-
-  std::unique_ptr<TimerFactory> timer_factory_;
-  std::unordered_map<std::string, AdvertisementWithTimer>
-      device_id_to_advertisement_with_timer_map_;
-
-  base::WeakPtrFactory<AdHocBleAdvertiserImpl> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(AdHocBleAdvertiserImpl);
-};
-
-}  // namespace tether
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_COMPONENTS_TETHER_AD_HOC_BLE_ADVERTISER_IMPL_H_
diff --git a/chromeos/components/tether/ad_hoc_ble_advertiser_impl_unittest.cc b/chromeos/components/tether/ad_hoc_ble_advertiser_impl_unittest.cc
deleted file mode 100644
index 50100c0..0000000
--- a/chromeos/components/tether/ad_hoc_ble_advertiser_impl_unittest.cc
+++ /dev/null
@@ -1,323 +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.
-
-#include "chromeos/components/tether/ad_hoc_ble_advertiser_impl.h"
-
-#include "base/bind.h"
-#include "base/callback_forward.h"
-#include "base/memory/ptr_util.h"
-#include "base/stl_util.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/test/test_simple_task_runner.h"
-#include "base/timer/mock_timer.h"
-#include "chromeos/components/tether/ble_constants.h"
-#include "chromeos/components/tether/error_tolerant_ble_advertisement_impl.h"
-#include "chromeos/components/tether/fake_ble_synchronizer.h"
-#include "chromeos/components/tether/fake_error_tolerant_ble_advertisement.h"
-#include "chromeos/components/tether/timer_factory.h"
-#include "components/cryptauth/ble/ble_advertisement_generator.h"
-#include "components/cryptauth/ble/fake_ble_advertisement_generator.h"
-#include "components/cryptauth/mock_foreground_eid_generator.h"
-#include "components/cryptauth/mock_local_device_data_provider.h"
-#include "components/cryptauth/mock_remote_beacon_seed_fetcher.h"
-#include "components/cryptauth/proto/cryptauth_api.pb.h"
-#include "components/cryptauth/remote_device_test_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace chromeos {
-
-namespace tether {
-
-namespace {
-
-constexpr const int64_t kNumSecondsToAdvertise = 12;
-
-std::vector<cryptauth::DataWithTimestamp> GenerateFakeAdvertisements() {
-  cryptauth::DataWithTimestamp advertisement1("advertisement1", 1000L, 2000L);
-  cryptauth::DataWithTimestamp advertisement2("advertisement2", 2000L, 3000L);
-
-  std::vector<cryptauth::DataWithTimestamp> advertisements = {advertisement1,
-                                                              advertisement2};
-  return advertisements;
-}
-
-class FakeErrorTolerantBleAdvertisementFactory final
-    : public ErrorTolerantBleAdvertisementImpl::Factory {
- public:
-  FakeErrorTolerantBleAdvertisementFactory() {}
-  ~FakeErrorTolerantBleAdvertisementFactory() override {}
-
-  const std::vector<FakeErrorTolerantBleAdvertisement*>&
-  active_advertisements() {
-    return active_advertisements_;
-  }
-
-  size_t num_created() { return num_created_; }
-
-  // ErrorTolerantBleAdvertisementImpl::Factory:
-  std::unique_ptr<ErrorTolerantBleAdvertisement> BuildInstance(
-      const std::string& device_id,
-      std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data,
-      BleSynchronizerBase* ble_synchronizer) override {
-    FakeErrorTolerantBleAdvertisement* fake_advertisement =
-        new FakeErrorTolerantBleAdvertisement(
-            device_id, base::Bind(&FakeErrorTolerantBleAdvertisementFactory::
-                                      OnFakeAdvertisementDeleted,
-                                  base::Unretained(this)));
-    active_advertisements_.push_back(fake_advertisement);
-    ++num_created_;
-    return base::WrapUnique(fake_advertisement);
-  }
-
- protected:
-  void OnFakeAdvertisementDeleted(
-      FakeErrorTolerantBleAdvertisement* fake_advertisement) {
-    EXPECT_TRUE(std::find(active_advertisements_.begin(),
-                          active_advertisements_.end(),
-                          fake_advertisement) != active_advertisements_.end());
-    base::Erase(active_advertisements_, fake_advertisement);
-  }
-
- private:
-  std::vector<FakeErrorTolerantBleAdvertisement*> active_advertisements_;
-  size_t num_created_ = 0;
-};
-
-class TestObserver final : public AdHocBleAdvertiser::Observer {
- public:
-  TestObserver() {}
-  ~TestObserver() override {}
-
-  size_t num_times_shutdown_complete() { return num_times_shutdown_complete_; }
-
-  // AdHocBleAdvertiser::Observer:
-  void OnAsynchronousShutdownComplete() override {
-    ++num_times_shutdown_complete_;
-  }
-
- private:
-  size_t num_times_shutdown_complete_ = 0;
-};
-
-class TestTimerFactory : public TimerFactory {
- public:
-  ~TestTimerFactory() override {}
-
-  // TimerFactory:
-  std::unique_ptr<base::Timer> CreateOneShotTimer() override {
-    EXPECT_FALSE(device_id_for_next_timer_.empty());
-    base::MockTimer* mock_timer = new base::MockTimer(
-        false /* retain_user_task */, false /* is_repeating */);
-    device_id_to_timer_map_[device_id_for_next_timer_] = mock_timer;
-    return base::WrapUnique(mock_timer);
-  }
-
-  base::MockTimer* GetTimerForDeviceId(const std::string& device_id) {
-    return device_id_to_timer_map_[device_id];
-  }
-
-  void set_device_id_for_next_timer(
-      const std::string& device_id_for_next_timer) {
-    device_id_for_next_timer_ = device_id_for_next_timer;
-  }
-
- private:
-  std::string device_id_for_next_timer_;
-  std::unordered_map<std::string, base::MockTimer*> device_id_to_timer_map_;
-};
-
-}  // namespace
-
-class AdHocBleAdvertiserImplTest : public testing::Test {
- protected:
-  AdHocBleAdvertiserImplTest()
-      : fake_devices_(cryptauth::CreateRemoteDeviceRefListForTest(2)),
-        fake_advertisements_(GenerateFakeAdvertisements()) {}
-
-  void SetUp() override {
-    fake_generator_ =
-        std::make_unique<cryptauth::FakeBleAdvertisementGenerator>();
-    cryptauth::BleAdvertisementGenerator::SetInstanceForTesting(
-        fake_generator_.get());
-
-    fake_advertisement_factory_ =
-        base::WrapUnique(new FakeErrorTolerantBleAdvertisementFactory());
-    ErrorTolerantBleAdvertisementImpl::Factory::SetInstanceForTesting(
-        fake_advertisement_factory_.get());
-
-    mock_seed_fetcher_ =
-        std::make_unique<cryptauth::MockRemoteBeaconSeedFetcher>();
-    mock_local_data_provider_ =
-        std::make_unique<cryptauth::MockLocalDeviceDataProvider>();
-    fake_ble_synchronizer_ = std::make_unique<FakeBleSynchronizer>();
-
-    workaround_ = std::make_unique<AdHocBleAdvertiserImpl>(
-        mock_local_data_provider_.get(), mock_seed_fetcher_.get(),
-        fake_ble_synchronizer_.get());
-
-    test_timer_factory_ = new TestTimerFactory();
-    workaround_->SetTimerFactoryForTesting(
-        base::WrapUnique(test_timer_factory_));
-
-    test_observer_ = base::WrapUnique(new TestObserver());
-    workaround_->AddObserver(test_observer_.get());
-  }
-
-  void TearDown() override {
-    ErrorTolerantBleAdvertisementImpl::Factory::SetInstanceForTesting(nullptr);
-    cryptauth::BleAdvertisementGenerator::SetInstanceForTesting(nullptr);
-  }
-
-  void SetAdvertisement(size_t index) {
-    fake_generator_->set_advertisement(
-        std::make_unique<cryptauth::DataWithTimestamp>(
-            fake_advertisements_[0]));
-  }
-
-  void FireTimer(cryptauth::RemoteDeviceRef remote_device) {
-    base::MockTimer* timer =
-        test_timer_factory_->GetTimerForDeviceId(remote_device.GetDeviceId());
-    ASSERT_TRUE(timer);
-    EXPECT_EQ(base::TimeDelta::FromSeconds(kNumSecondsToAdvertise),
-              timer->GetCurrentDelay());
-    timer->Fire();
-  }
-
-  void FinishStoppingAdvertisement(size_t expected_index,
-                                   cryptauth::RemoteDeviceRef remote_device) {
-    FakeErrorTolerantBleAdvertisement* advertisement =
-        fake_advertisement_factory_->active_advertisements()[expected_index];
-    ASSERT_TRUE(advertisement);
-    EXPECT_EQ(remote_device.GetDeviceId(), advertisement->device_id());
-    advertisement->InvokeStopCallback();
-  }
-
-  const cryptauth::RemoteDeviceRefList fake_devices_;
-  const std::vector<cryptauth::DataWithTimestamp> fake_advertisements_;
-
-  std::unique_ptr<cryptauth::MockRemoteBeaconSeedFetcher> mock_seed_fetcher_;
-  std::unique_ptr<cryptauth::MockLocalDeviceDataProvider>
-      mock_local_data_provider_;
-  std::unique_ptr<FakeBleSynchronizer> fake_ble_synchronizer_;
-
-  std::unique_ptr<cryptauth::FakeBleAdvertisementGenerator> fake_generator_;
-  TestTimerFactory* test_timer_factory_;
-
-  std::unique_ptr<TestObserver> test_observer_;
-
-  std::unique_ptr<FakeErrorTolerantBleAdvertisementFactory>
-      fake_advertisement_factory_;
-
-  std::unique_ptr<AdHocBleAdvertiserImpl> workaround_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(AdHocBleAdvertiserImplTest);
-};
-
-TEST_F(AdHocBleAdvertiserImplTest, CannotGenerateAdvertisement) {
-  fake_generator_->set_advertisement(nullptr);
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_EQ(0u, fake_advertisement_factory_->num_created());
-  EXPECT_EQ(0u, test_observer_->num_times_shutdown_complete());
-  EXPECT_FALSE(workaround_->HasPendingRequests());
-}
-
-TEST_F(AdHocBleAdvertiserImplTest, AdvertiseAndStop) {
-  SetAdvertisement(0 /* index */);
-  test_timer_factory_->set_device_id_for_next_timer(
-      fake_devices_[0].GetDeviceId());
-
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  FireTimer(fake_devices_[0]);
-  EXPECT_EQ(0u, test_observer_->num_times_shutdown_complete());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-
-  FinishStoppingAdvertisement(0u /* expected_index */, fake_devices_[0]);
-  EXPECT_FALSE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, test_observer_->num_times_shutdown_complete());
-}
-
-TEST_F(AdHocBleAdvertiserImplTest, TwoRequestsForSameDevice_BeforeTimer) {
-  SetAdvertisement(0 /* index */);
-  test_timer_factory_->set_device_id_for_next_timer(
-      fake_devices_[0].GetDeviceId());
-
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  // No additional advertisement should be created.
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  FireTimer(fake_devices_[0]);
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-
-  FinishStoppingAdvertisement(0u /* expected_index */, fake_devices_[0]);
-  EXPECT_FALSE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, test_observer_->num_times_shutdown_complete());
-}
-
-TEST_F(AdHocBleAdvertiserImplTest, TwoRequestsForSameDevice_AfterTimer) {
-  SetAdvertisement(0 /* index */);
-  test_timer_factory_->set_device_id_for_next_timer(
-      fake_devices_[0].GetDeviceId());
-
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  FireTimer(fake_devices_[0]);
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-
-  // No additional advertisement should be created.
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  FinishStoppingAdvertisement(0u /* expected_index */, fake_devices_[0]);
-  EXPECT_FALSE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, test_observer_->num_times_shutdown_complete());
-}
-
-TEST_F(AdHocBleAdvertiserImplTest, TwoRequestsForDifferentDevices) {
-  SetAdvertisement(0 /* index */);
-  test_timer_factory_->set_device_id_for_next_timer(
-      fake_devices_[0].GetDeviceId());
-  workaround_->RequestGattServicesForDevice(fake_devices_[0].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, fake_advertisement_factory_->num_created());
-
-  SetAdvertisement(1 /* index */);
-  test_timer_factory_->set_device_id_for_next_timer(
-      fake_devices_[1].GetDeviceId());
-  workaround_->RequestGattServicesForDevice(fake_devices_[1].GetDeviceId());
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(2u, fake_advertisement_factory_->num_created());
-
-  FireTimer(fake_devices_[0]);
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-
-  // Finish stopping the 0th device. There should still be pending requests.
-  FinishStoppingAdvertisement(0u /* expected_index */, fake_devices_[0]);
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-  EXPECT_EQ(0u, test_observer_->num_times_shutdown_complete());
-
-  FireTimer(fake_devices_[1]);
-  EXPECT_TRUE(workaround_->HasPendingRequests());
-
-  // |expected_index| is still 0u, since the other advertisement was deleted
-  // from the vector.
-  FinishStoppingAdvertisement(0u /* expected_index */, fake_devices_[1]);
-  EXPECT_FALSE(workaround_->HasPendingRequests());
-  EXPECT_EQ(1u, test_observer_->num_times_shutdown_complete());
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/asynchronous_shutdown_object_container_impl.cc b/chromeos/components/tether/asynchronous_shutdown_object_container_impl.cc
index ecf11e7c..ccd385ca 100644
--- a/chromeos/components/tether/asynchronous_shutdown_object_container_impl.cc
+++ b/chromeos/components/tether/asynchronous_shutdown_object_container_impl.cc
@@ -5,7 +5,6 @@
 #include "chromeos/components/tether/asynchronous_shutdown_object_container_impl.h"
 
 #include "base/memory/ptr_util.h"
-#include "chromeos/components/tether/ad_hoc_ble_advertiser_impl.h"
 #include "chromeos/components/tether/ble_advertisement_device_queue.h"
 #include "chromeos/components/tether/ble_advertiser_impl.h"
 #include "chromeos/components/tether/ble_connection_manager.h"
@@ -15,6 +14,7 @@
 #include "chromeos/components/tether/disconnect_tethering_request_sender_impl.h"
 #include "chromeos/components/tether/network_configuration_remover.h"
 #include "chromeos/components/tether/wifi_hotspot_disconnector_impl.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "components/cryptauth/cryptauth_service.h"
 #include "components/cryptauth/local_device_data_provider.h"
 #include "components/cryptauth/remote_beacon_seed_fetcher.h"
@@ -33,6 +33,7 @@
 AsynchronousShutdownObjectContainerImpl::Factory::NewInstance(
     scoped_refptr<device::BluetoothAdapter> adapter,
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     TetherHostFetcher* tether_host_fetcher,
     NetworkStateHandler* network_state_handler,
     ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
@@ -42,9 +43,9 @@
     factory_instance_ = new Factory();
 
   return factory_instance_->BuildInstance(
-      adapter, cryptauth_service, tether_host_fetcher, network_state_handler,
-      managed_network_configuration_handler, network_connection_handler,
-      pref_service);
+      adapter, cryptauth_service, device_sync_client, tether_host_fetcher,
+      network_state_handler, managed_network_configuration_handler,
+      network_connection_handler, pref_service);
 }
 
 // static
@@ -59,21 +60,23 @@
 AsynchronousShutdownObjectContainerImpl::Factory::BuildInstance(
     scoped_refptr<device::BluetoothAdapter> adapter,
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     TetherHostFetcher* tether_host_fetcher,
     NetworkStateHandler* network_state_handler,
     ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
     NetworkConnectionHandler* network_connection_handler,
     PrefService* pref_service) {
   return base::WrapUnique(new AsynchronousShutdownObjectContainerImpl(
-      adapter, cryptauth_service, tether_host_fetcher, network_state_handler,
-      managed_network_configuration_handler, network_connection_handler,
-      pref_service));
+      adapter, cryptauth_service, device_sync_client, tether_host_fetcher,
+      network_state_handler, managed_network_configuration_handler,
+      network_connection_handler, pref_service));
 }
 
 AsynchronousShutdownObjectContainerImpl::
     AsynchronousShutdownObjectContainerImpl(
         scoped_refptr<device::BluetoothAdapter> adapter,
         cryptauth::CryptAuthService* cryptauth_service,
+        chromeos::device_sync::DeviceSyncClient* device_sync_client,
         TetherHostFetcher* tether_host_fetcher,
         NetworkStateHandler* network_state_handler,
         ManagedNetworkConfigurationHandler*
@@ -101,16 +104,11 @@
           remote_beacon_seed_fetcher_.get(),
           ble_synchronizer_.get(),
           tether_host_fetcher_)),
-      ad_hoc_ble_advertiser_(std::make_unique<AdHocBleAdvertiserImpl>(
-          local_device_data_provider_.get(),
-          remote_beacon_seed_fetcher_.get(),
-          ble_synchronizer_.get())),
       ble_connection_manager_(std::make_unique<BleConnectionManager>(
           adapter,
           ble_advertisement_device_queue_.get(),
           ble_advertiser_.get(),
-          ble_scanner_.get(),
-          ad_hoc_ble_advertiser_.get())),
+          ble_scanner_.get())),
       ble_connection_metrics_logger_(
           std::make_unique<BleConnectionMetricsLogger>()),
       disconnect_tethering_request_sender_(
@@ -137,7 +135,6 @@
   ble_advertiser_->RemoveObserver(this);
   ble_scanner_->RemoveObserver(this);
   disconnect_tethering_request_sender_->RemoveObserver(this);
-  ad_hoc_ble_advertiser_->RemoveObserver(this);
 }
 
 void AsynchronousShutdownObjectContainerImpl::Shutdown(
@@ -151,7 +148,6 @@
   ble_advertiser_->AddObserver(this);
   ble_scanner_->AddObserver(this);
   disconnect_tethering_request_sender_->AddObserver(this);
-  ad_hoc_ble_advertiser_->AddObserver(this);
 
   ShutdownIfPossible();
 }
@@ -191,10 +187,6 @@
   ShutdownIfPossible();
 }
 
-void AsynchronousShutdownObjectContainerImpl::OnAsynchronousShutdownComplete() {
-  ShutdownIfPossible();
-}
-
 void AsynchronousShutdownObjectContainerImpl::OnDiscoverySessionStateChanged(
     bool discovery_session_active) {
   ShutdownIfPossible();
@@ -209,7 +201,6 @@
   ble_advertiser_->RemoveObserver(this);
   ble_scanner_->RemoveObserver(this);
   disconnect_tethering_request_sender_->RemoveObserver(this);
-  ad_hoc_ble_advertiser_->RemoveObserver(this);
 
   shutdown_complete_callback_.Run();
 }
@@ -237,10 +228,6 @@
   if (ble_advertiser_->AreAdvertisementsRegistered())
     return true;
 
-  // Likewise, the ad hoc BLE advertiser must be shut down.
-  if (ad_hoc_ble_advertiser_->HasPendingRequests())
-    return true;
-
   return false;
 }
 
@@ -248,13 +235,11 @@
     std::unique_ptr<BleAdvertiser> ble_advertiser,
     std::unique_ptr<BleScanner> ble_scanner,
     std::unique_ptr<DisconnectTetheringRequestSender>
-        disconnect_tethering_request_sender,
-    std::unique_ptr<AdHocBleAdvertiser> ad_hoc_ble_advertiser) {
+        disconnect_tethering_request_sender) {
   ble_advertiser_ = std::move(ble_advertiser);
   ble_scanner_ = std::move(ble_scanner);
   disconnect_tethering_request_sender_ =
       std::move(disconnect_tethering_request_sender);
-  ad_hoc_ble_advertiser_ = std::move(ad_hoc_ble_advertiser);
 }
 
 }  // namespace tether
diff --git a/chromeos/components/tether/asynchronous_shutdown_object_container_impl.h b/chromeos/components/tether/asynchronous_shutdown_object_container_impl.h
index 7ec826e..edef3da 100644
--- a/chromeos/components/tether/asynchronous_shutdown_object_container_impl.h
+++ b/chromeos/components/tether/asynchronous_shutdown_object_container_impl.h
@@ -10,7 +10,6 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "chromeos/components/tether/ad_hoc_ble_advertiser.h"
 #include "chromeos/components/tether/asynchronous_shutdown_object_container.h"
 #include "chromeos/components/tether/ble_advertiser.h"
 #include "chromeos/components/tether/ble_scanner.h"
@@ -34,6 +33,10 @@
 class NetworkConnectionHandler;
 class NetworkStateHandler;
 
+namespace device_sync {
+class DeviceSyncClient;
+}  // namespace device_sync
+
 namespace tether {
 
 class BleAdvertisementDeviceQueue;
@@ -49,14 +52,14 @@
     : public AsynchronousShutdownObjectContainer,
       public BleAdvertiser::Observer,
       public BleScanner::Observer,
-      public DisconnectTetheringRequestSender::Observer,
-      public AdHocBleAdvertiser::Observer {
+      public DisconnectTetheringRequestSender::Observer {
  public:
   class Factory {
    public:
     static std::unique_ptr<AsynchronousShutdownObjectContainer> NewInstance(
         scoped_refptr<device::BluetoothAdapter> adapter,
         cryptauth::CryptAuthService* cryptauth_service,
+        chromeos::device_sync::DeviceSyncClient* device_sync_client,
         TetherHostFetcher* tether_host_fetcher,
         NetworkStateHandler* network_state_handler,
         ManagedNetworkConfigurationHandler*
@@ -69,6 +72,7 @@
     virtual std::unique_ptr<AsynchronousShutdownObjectContainer> BuildInstance(
         scoped_refptr<device::BluetoothAdapter> adapter,
         cryptauth::CryptAuthService* cryptauth_service,
+        chromeos::device_sync::DeviceSyncClient* device_sync_client,
         TetherHostFetcher* tether_host_fetcher,
         NetworkStateHandler* network_state_handler,
         ManagedNetworkConfigurationHandler*
@@ -96,6 +100,7 @@
   AsynchronousShutdownObjectContainerImpl(
       scoped_refptr<device::BluetoothAdapter> adapter,
       cryptauth::CryptAuthService* cryptauth_service,
+      chromeos::device_sync::DeviceSyncClient* device_sync_client,
       TetherHostFetcher* tether_host_fetcher,
       NetworkStateHandler* network_state_handler,
       ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
@@ -111,21 +116,16 @@
   // DisconnectTetheringRequestSender::Observer:
   void OnPendingDisconnectRequestsComplete() override;
 
-  // AdHocBleAdvertiser::Observer:
-  void OnAsynchronousShutdownComplete() override;
-
  private:
   friend class AsynchronousShutdownObjectContainerImplTest;
 
   void ShutdownIfPossible();
   bool AreAsynchronousOperationsActive();
 
-  void SetTestDoubles(
-      std::unique_ptr<BleAdvertiser> ble_advertiser,
-      std::unique_ptr<BleScanner> ble_scanner,
-      std::unique_ptr<DisconnectTetheringRequestSender>
-          disconnect_tethering_request_sender,
-      std::unique_ptr<AdHocBleAdvertiser> ad_hoc_ble_advertiser);
+  void SetTestDoubles(std::unique_ptr<BleAdvertiser> ble_advertiser,
+                      std::unique_ptr<BleScanner> ble_scanner,
+                      std::unique_ptr<DisconnectTetheringRequestSender>
+                          disconnect_tethering_request_sender);
 
   scoped_refptr<device::BluetoothAdapter> adapter_;
 
@@ -138,7 +138,6 @@
   std::unique_ptr<BleSynchronizer> ble_synchronizer_;
   std::unique_ptr<BleAdvertiser> ble_advertiser_;
   std::unique_ptr<BleScanner> ble_scanner_;
-  std::unique_ptr<AdHocBleAdvertiser> ad_hoc_ble_advertiser_;
   std::unique_ptr<BleConnectionManager> ble_connection_manager_;
   std::unique_ptr<BleConnectionMetricsLogger> ble_connection_metrics_logger_;
   std::unique_ptr<DisconnectTetheringRequestSender>
diff --git a/chromeos/components/tether/asynchronous_shutdown_object_container_impl_unittest.cc b/chromeos/components/tether/asynchronous_shutdown_object_container_impl_unittest.cc
index 6f76ffa..b7af23a 100644
--- a/chromeos/components/tether/asynchronous_shutdown_object_container_impl_unittest.cc
+++ b/chromeos/components/tether/asynchronous_shutdown_object_container_impl_unittest.cc
@@ -9,11 +9,11 @@
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/test/scoped_task_environment.h"
-#include "chromeos/components/tether/fake_ad_hoc_ble_advertiser.h"
 #include "chromeos/components/tether/fake_ble_advertiser.h"
 #include "chromeos/components/tether/fake_ble_scanner.h"
 #include "chromeos/components/tether/fake_disconnect_tethering_request_sender.h"
 #include "chromeos/components/tether/tether_component_impl.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "components/cryptauth/fake_cryptauth_service.h"
 #include "components/cryptauth/fake_remote_device_provider.h"
 #include "components/cryptauth/remote_device_provider_impl.h"
@@ -72,6 +72,8 @@
 
     fake_cryptauth_service_ =
         std::make_unique<cryptauth::FakeCryptAuthService>();
+    fake_device_sync_client_ =
+        std::make_unique<chromeos::device_sync::FakeDeviceSyncClient>();
 
     test_pref_service_ =
         std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
@@ -82,7 +84,8 @@
     // of objects created by the container.
     container_ = base::WrapUnique(new AsynchronousShutdownObjectContainerImpl(
         mock_adapter_, fake_cryptauth_service_.get(),
-        nullptr /* tether_host_fetcher */, nullptr /* network_state_handler */,
+        fake_device_sync_client_.get(), nullptr /* tether_host_fetcher */,
+        nullptr /* network_state_handler */,
         nullptr /* managed_network_configuration_handler */,
         nullptr /* network_connection_handler */,
         test_pref_service_.get() /* pref_service */));
@@ -93,13 +96,11 @@
         new FakeBleScanner(false /* automatically_update_discovery_session */);
     fake_disconnect_tethering_request_sender_ =
         new FakeDisconnectTetheringRequestSender();
-    fake_ad_hoc_ble_advertisement_ = new FakeAdHocBleAdvertiser();
 
     container_->SetTestDoubles(
         base::WrapUnique(fake_ble_advertiser_),
         base::WrapUnique(fake_ble_scanner_),
-        base::WrapUnique(fake_disconnect_tethering_request_sender_),
-        base::WrapUnique(fake_ad_hoc_ble_advertisement_));
+        base::WrapUnique(fake_disconnect_tethering_request_sender_));
   }
 
   bool MockIsAdapterPowered() { return is_adapter_powered_; }
@@ -117,6 +118,8 @@
 
   scoped_refptr<NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
   std::unique_ptr<cryptauth::FakeCryptAuthService> fake_cryptauth_service_;
+  std::unique_ptr<chromeos::device_sync::FakeDeviceSyncClient>
+      fake_device_sync_client_;
   std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
       test_pref_service_;
   std::unique_ptr<FakeRemoteDeviceProviderFactory>
@@ -125,7 +128,6 @@
   FakeBleScanner* fake_ble_scanner_;
   FakeDisconnectTetheringRequestSender*
       fake_disconnect_tethering_request_sender_;
-  FakeAdHocBleAdvertiser* fake_ad_hoc_ble_advertisement_;
 
   bool was_shutdown_callback_invoked_;
   bool is_adapter_powered_;
@@ -197,23 +199,6 @@
 }
 
 TEST_F(AsynchronousShutdownObjectContainerImplTest,
-       TestShutdown_AsyncAdHocBleAdvertiserShutdown) {
-  fake_ad_hoc_ble_advertisement_->set_has_pending_requests(true);
-  EXPECT_TRUE(fake_ad_hoc_ble_advertisement_->HasPendingRequests());
-
-  // Start the shutdown; it should not yet succeed since there are pending
-  // requests.
-  CallShutdown();
-  EXPECT_FALSE(was_shutdown_callback_invoked_);
-
-  // Now, finish the pending requests; this should cause the shutdown to
-  // complete.
-  fake_ad_hoc_ble_advertisement_->set_has_pending_requests(false);
-  fake_ad_hoc_ble_advertisement_->NotifyAsynchronousShutdownComplete();
-  EXPECT_TRUE(was_shutdown_callback_invoked_);
-}
-
-TEST_F(AsynchronousShutdownObjectContainerImplTest,
        TestShutdown_MultipleSimultaneousAsyncShutdowns) {
   fake_ble_advertiser_->set_are_advertisements_registered(true);
   EXPECT_TRUE(fake_ble_advertiser_->AreAdvertisementsRegistered());
@@ -225,9 +210,6 @@
   fake_disconnect_tethering_request_sender_->set_has_pending_requests(true);
   EXPECT_TRUE(fake_disconnect_tethering_request_sender_->HasPendingRequests());
 
-  fake_ad_hoc_ble_advertisement_->set_has_pending_requests(true);
-  EXPECT_TRUE(fake_ad_hoc_ble_advertisement_->HasPendingRequests());
-
   // Start the shutdown; it should not yet succeed since there are pending
   // requests.
   CallShutdown();
@@ -240,12 +222,6 @@
   fake_ble_advertiser_->NotifyAllAdvertisementsUnregistered();
   EXPECT_FALSE(was_shutdown_callback_invoked_);
 
-  // Now, finish GATT services workaround shutdown; this should not cause the
-  // shutdown to complete since there are still pending requests
-  fake_ad_hoc_ble_advertisement_->set_has_pending_requests(false);
-  fake_ad_hoc_ble_advertisement_->NotifyAsynchronousShutdownComplete();
-  EXPECT_FALSE(was_shutdown_callback_invoked_);
-
   // Now, remove the discovery session; this should not cause the shutdown to
   // complete since there are still pending requests.
   fake_ble_scanner_->set_is_discovery_session_active(false);
diff --git a/chromeos/components/tether/ble_advertisement_device_queue.cc b/chromeos/components/tether/ble_advertisement_device_queue.cc
index c345ec2..52c3247 100644
--- a/chromeos/components/tether/ble_advertisement_device_queue.cc
+++ b/chromeos/components/tether/ble_advertisement_device_queue.cc
@@ -16,7 +16,7 @@
 
 BleAdvertisementDeviceQueue::PrioritizedDeviceId::PrioritizedDeviceId(
     const std::string& device_id,
-    const ConnectionPriority& connection_priority)
+    const secure_channel::ConnectionPriority& connection_priority)
     : device_id(device_id), connection_priority(connection_priority) {}
 
 BleAdvertisementDeviceQueue::PrioritizedDeviceId::~PrioritizedDeviceId() =
@@ -62,7 +62,7 @@
   // were not provided as part of the |prioritized_ids| parameter. If any such
   // entries exist, remove them from the map.
   for (auto& map_entry : priority_to_device_ids_map_) {
-    ConnectionPriority priority = map_entry.first;
+    secure_channel::ConnectionPriority priority = map_entry.first;
     std::vector<std::string>& device_ids_for_priority = map_entry.second;
 
     auto device_ids_it = device_ids_for_priority.begin();
@@ -109,11 +109,11 @@
 std::vector<std::string>
 BleAdvertisementDeviceQueue::GetDeviceIdsToWhichToAdvertise() const {
   std::vector<std::string> device_ids;
-  AddDevicesToVectorForPriority(ConnectionPriority::CONNECTION_PRIORITY_HIGH,
+  AddDevicesToVectorForPriority(secure_channel::ConnectionPriority::kHigh,
                                 &device_ids);
-  AddDevicesToVectorForPriority(ConnectionPriority::CONNECTION_PRIORITY_MEDIUM,
+  AddDevicesToVectorForPriority(secure_channel::ConnectionPriority::kMedium,
                                 &device_ids);
-  AddDevicesToVectorForPriority(ConnectionPriority::CONNECTION_PRIORITY_LOW,
+  AddDevicesToVectorForPriority(secure_channel::ConnectionPriority::kLow,
                                 &device_ids);
   DCHECK(device_ids.size() <= kMaxConcurrentAdvertisements);
   return device_ids;
@@ -127,7 +127,7 @@
 }
 
 void BleAdvertisementDeviceQueue::AddDevicesToVectorForPriority(
-    ConnectionPriority connection_priority,
+    secure_channel::ConnectionPriority connection_priority,
     std::vector<std::string>* device_ids_out) const {
   if (priority_to_device_ids_map_.find(connection_priority) ==
       priority_to_device_ids_map_.end()) {
diff --git a/chromeos/components/tether/ble_advertisement_device_queue.h b/chromeos/components/tether/ble_advertisement_device_queue.h
index e57812b..847f7b2 100644
--- a/chromeos/components/tether/ble_advertisement_device_queue.h
+++ b/chromeos/components/tether/ble_advertisement_device_queue.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "chromeos/components/tether/connection_priority.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -26,12 +26,13 @@
   virtual ~BleAdvertisementDeviceQueue();
 
   struct PrioritizedDeviceId {
-    PrioritizedDeviceId(const std::string& device_id,
-                        const ConnectionPriority& connection_priority);
+    PrioritizedDeviceId(
+        const std::string& device_id,
+        const secure_channel::ConnectionPriority& connection_priority);
     ~PrioritizedDeviceId();
 
     std::string device_id;
-    ConnectionPriority connection_priority;
+    secure_channel::ConnectionPriority connection_priority;
   };
 
   // Updates the queue with the given |prioritized_ids|. Devices which are
@@ -71,10 +72,10 @@
       const std::vector<PrioritizedDeviceId>& prioritized_ids);
 
   void AddDevicesToVectorForPriority(
-      ConnectionPriority connection_priority,
+      secure_channel::ConnectionPriority connection_priority,
       std::vector<std::string>* device_ids_out) const;
 
-  std::map<ConnectionPriority, std::vector<std::string>>
+  std::map<secure_channel::ConnectionPriority, std::vector<std::string>>
       priority_to_device_ids_map_;
 
   DISALLOW_COPY_AND_ASSIGN(BleAdvertisementDeviceQueue);
diff --git a/chromeos/components/tether/ble_advertisement_device_queue_unittest.cc b/chromeos/components/tether/ble_advertisement_device_queue_unittest.cc
index ed1d4d1..9c47ecf 100644
--- a/chromeos/components/tether/ble_advertisement_device_queue_unittest.cc
+++ b/chromeos/components/tether/ble_advertisement_device_queue_unittest.cc
@@ -46,7 +46,7 @@
 TEST_F(BleAdvertisementDeviceQueueTest, TestSingleDevice) {
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(1u, device_queue_->GetSize());
 
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
@@ -56,7 +56,7 @@
 TEST_F(BleAdvertisementDeviceQueueTest, TestSingleDevice_MoveToEnd) {
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(1u, device_queue_->GetSize());
 
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
@@ -70,9 +70,9 @@
 TEST_F(BleAdvertisementDeviceQueueTest, TestTwoDevices) {
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(2u, device_queue_->GetSize());
 
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
@@ -83,9 +83,9 @@
 TEST_F(BleAdvertisementDeviceQueueTest, TestTwoDevices_MoveToEnd) {
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(2u, device_queue_->GetSize());
 
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
@@ -110,11 +110,11 @@
 
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[2].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(3u, device_queue_->GetSize());
 
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
@@ -144,9 +144,9 @@
 
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(2u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[0].GetDeviceId(),
@@ -160,13 +160,13 @@
   // Device 0 has been unregistered; devices 3 and 4 have been registered.
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[2].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[3].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[4].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH)}));
+                           secure_channel::ConnectionPriority::kHigh)}));
   EXPECT_EQ(4u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[1].GetDeviceId(),
@@ -190,15 +190,15 @@
 
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[2].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[3].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[4].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_LOW)}));
+                           secure_channel::ConnectionPriority::kLow)}));
   EXPECT_EQ(5u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[0].GetDeviceId(),
@@ -214,13 +214,13 @@
   // Device 0 has been unregistered; device 2 has moved to low-priority.
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_HIGH),
+                           secure_channel::ConnectionPriority::kHigh),
        PrioritizedDeviceId(test_devices_[2].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_LOW),
+                           secure_channel::ConnectionPriority::kLow),
        PrioritizedDeviceId(test_devices_[3].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[4].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_LOW)}));
+                           secure_channel::ConnectionPriority::kLow)}));
   EXPECT_EQ(4u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[1].GetDeviceId(),
@@ -245,13 +245,13 @@
   // were just added.
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[2].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[3].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM),
+                           secure_channel::ConnectionPriority::kMedium),
        PrioritizedDeviceId(test_devices_[4].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_MEDIUM)}));
+                           secure_channel::ConnectionPriority::kMedium)}));
   EXPECT_EQ(4u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[3].GetDeviceId(),
@@ -267,7 +267,7 @@
   // Leave only one low-priority device left.
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(
       {PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                           ConnectionPriority::CONNECTION_PRIORITY_LOW)}));
+                           secure_channel::ConnectionPriority::kLow)}));
   EXPECT_EQ(1u, device_queue_->GetSize());
   EXPECT_THAT(device_queue_->GetDeviceIdsToWhichToAdvertise(),
               ElementsAre(test_devices_[1].GetDeviceId()));
@@ -282,7 +282,7 @@
 TEST_F(BleAdvertisementDeviceQueueTest, TestSettingSameDevices) {
   std::vector<PrioritizedDeviceId> prioritized_devices = {
       PrioritizedDeviceId(test_devices_[0].GetDeviceId(),
-                          ConnectionPriority::CONNECTION_PRIORITY_HIGH)};
+                          secure_channel::ConnectionPriority::kHigh)};
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(prioritized_devices));
 
   // Setting the same devices again should return false.
@@ -291,7 +291,7 @@
 
   prioritized_devices.push_back(
       PrioritizedDeviceId(test_devices_[1].GetDeviceId(),
-                          ConnectionPriority::CONNECTION_PRIORITY_HIGH));
+                          secure_channel::ConnectionPriority::kHigh));
   EXPECT_TRUE(device_queue_->SetPrioritizedDeviceIds(prioritized_devices));
 }
 
diff --git a/chromeos/components/tether/ble_connection_manager.cc b/chromeos/components/tether/ble_connection_manager.cc
index 48b6b3e..66d24b9 100644
--- a/chromeos/components/tether/ble_connection_manager.cc
+++ b/chromeos/components/tether/ble_connection_manager.cc
@@ -7,7 +7,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
-#include "chromeos/components/tether/ad_hoc_ble_advertiser.h"
 #include "chromeos/components/tether/ble_constants.h"
 #include "chromeos/components/tether/timer_factory.h"
 #include "components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.h"
@@ -61,23 +60,36 @@
 
 BleConnectionManager::ConnectionMetadata::~ConnectionMetadata() = default;
 
-void BleConnectionManager::ConnectionMetadata::RegisterConnectionReason(
-    const ConnectionReason& connection_reason) {
-  active_connection_reasons_.insert(connection_reason);
+void BleConnectionManager::ConnectionMetadata::RegisterConnectionRequest(
+    const base::UnguessableToken& request_id,
+    secure_channel::ConnectionPriority connection_priority) {
+  DCHECK(!base::ContainsKey(request_id_to_priority_map_, request_id));
+  request_id_to_priority_map_.insert(
+      std::make_pair(request_id, connection_priority));
 }
 
-void BleConnectionManager::ConnectionMetadata::UnregisterConnectionReason(
-    const ConnectionReason& connection_reason) {
-  active_connection_reasons_.erase(connection_reason);
+void BleConnectionManager::ConnectionMetadata::UnregisterConnectionRequest(
+    const base::UnguessableToken& request_id) {
+  request_id_to_priority_map_.erase(request_id);
 }
 
-ConnectionPriority
+secure_channel::ConnectionPriority
 BleConnectionManager::ConnectionMetadata::GetConnectionPriority() {
-  return HighestPriorityForConnectionReasons(active_connection_reasons_);
+  DCHECK(HasPendingConnectionRequests());
+
+  secure_channel::ConnectionPriority highest_priority =
+      secure_channel::ConnectionPriority::kLow;
+  for (const auto& map_entry : request_id_to_priority_map_) {
+    if (map_entry.second > highest_priority)
+      highest_priority = map_entry.second;
+  }
+
+  return highest_priority;
 }
 
-bool BleConnectionManager::ConnectionMetadata::HasReasonForConnection() const {
-  return !active_connection_reasons_.empty();
+bool BleConnectionManager::ConnectionMetadata::HasPendingConnectionRequests()
+    const {
+  return !request_id_to_priority_map_.empty();
 }
 
 bool BleConnectionManager::ConnectionMetadata::HasEstablishedConnection()
@@ -208,22 +220,15 @@
   manager_->NotifyMessageSent(sequence_number);
 }
 
-void BleConnectionManager::ConnectionMetadata::
-    OnGattCharacteristicsNotAvailable() {
-  manager_->OnGattCharacteristicsNotAvailable(device_id_);
-}
-
 BleConnectionManager::BleConnectionManager(
     scoped_refptr<device::BluetoothAdapter> adapter,
     BleAdvertisementDeviceQueue* ble_advertisement_device_queue,
     BleAdvertiser* ble_advertiser,
-    BleScanner* ble_scanner,
-    AdHocBleAdvertiser* ad_hoc_ble_advertisement)
+    BleScanner* ble_scanner)
     : adapter_(adapter),
       ble_advertisement_device_queue_(ble_advertisement_device_queue),
       ble_advertiser_(ble_advertiser),
       ble_scanner_(ble_scanner),
-      ad_hoc_ble_advertisement_(ad_hoc_ble_advertisement),
       timer_factory_(std::make_unique<TimerFactory>()),
       has_registered_observer_(false),
       weak_ptr_factory_(this) {}
@@ -236,7 +241,8 @@
 
 void BleConnectionManager::RegisterRemoteDevice(
     const std::string& device_id,
-    const ConnectionReason& connection_reason) {
+    const base::UnguessableToken& request_id,
+    secure_channel::ConnectionPriority connection_priority) {
   if (!has_registered_observer_) {
     ble_scanner_->AddObserver(this);
   }
@@ -244,36 +250,37 @@
 
   PA_LOG(INFO) << "Register - Device ID: \""
                << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
-               << "\", Reason: " << ConnectionReasonToString(connection_reason);
+               << "\", Request ID: " << request_id
+               << ", Priority: " << connection_priority;
 
   ConnectionMetadata* connection_metadata = GetConnectionMetadata(device_id);
   if (!connection_metadata)
     connection_metadata = AddMetadataForDevice(device_id);
 
-  connection_metadata->RegisterConnectionReason(connection_reason);
+  connection_metadata->RegisterConnectionRequest(request_id,
+                                                 connection_priority);
   UpdateConnectionAttempts();
 }
 
 void BleConnectionManager::UnregisterRemoteDevice(
     const std::string& device_id,
-    const ConnectionReason& connection_reason) {
+    const base::UnguessableToken& request_id) {
   ConnectionMetadata* connection_metadata = GetConnectionMetadata(device_id);
   if (!connection_metadata) {
     PA_LOG(WARNING) << "Tried to unregister device, but was not registered - "
                     << "Device ID: \""
                     << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
                            device_id)
-                    << "\", Reason: "
-                    << ConnectionReasonToString(connection_reason);
+                    << "\", Request ID: " << request_id;
     return;
   }
 
   PA_LOG(INFO) << "Unregister - Device ID: \""
                << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
-               << "\", Reason: " << ConnectionReasonToString(connection_reason);
+               << "\", Request ID: " << request_id;
 
-  connection_metadata->UnregisterConnectionReason(connection_reason);
-  if (!connection_metadata->HasReasonForConnection()) {
+  connection_metadata->UnregisterConnectionRequest(request_id);
+  if (!connection_metadata->HasPendingConnectionRequests()) {
     if (connection_metadata->HasEstablishedConnection()) {
       connection_metadata->Disconnect();
     } else {
@@ -560,7 +567,7 @@
   const cryptauth::SecureChannel::Status old_status_copy = old_status;
   const cryptauth::SecureChannel::Status new_status_copy = new_status;
 
-  if (!connection_metadata->HasReasonForConnection() &&
+  if (!connection_metadata->HasPendingConnectionRequests() &&
       new_status == cryptauth::SecureChannel::Status::DISCONNECTED) {
     device_id_to_metadata_map_.erase(device_id_copy);
     state_change_detail =
@@ -572,16 +579,6 @@
   UpdateConnectionAttempts();
 }
 
-void BleConnectionManager::OnGattCharacteristicsNotAvailable(
-    const std::string& device_id) {
-  PA_LOG(WARNING) << "Previous connection attempt failed due to unavailable "
-                  << "GATT services for device ID \""
-                  << cryptauth::RemoteDeviceRef::TruncateDeviceIdForLogs(
-                         device_id)
-                  << "\".";
-  ad_hoc_ble_advertisement_->RequestGattServicesForDevice(device_id);
-}
-
 void BleConnectionManager::NotifyAdvertisementReceived(
     const std::string& device_id,
     bool is_background_advertisement) {
diff --git a/chromeos/components/tether/ble_connection_manager.h b/chromeos/components/tether/ble_connection_manager.h
index 39323343..12c2ec6 100644
--- a/chromeos/components/tether/ble_connection_manager.h
+++ b/chromeos/components/tether/ble_connection_manager.h
@@ -8,18 +8,19 @@
 #include <map>
 #include <set>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/tether/ble_advertisement_device_queue.h"
 #include "chromeos/components/tether/ble_advertiser.h"
 #include "chromeos/components/tether/ble_scanner.h"
-#include "chromeos/components/tether/connection_priority.h"
-#include "chromeos/components/tether/connection_reason.h"
 #include "chromeos/components/tether/proto/tether.pb.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 #include "components/cryptauth/secure_channel.h"
 
 namespace device {
@@ -31,7 +32,6 @@
 
 namespace tether {
 
-class AdHocBleAdvertiser;
 class TimerFactory;
 
 // Manages connections to remote devices. When a device is registered,
@@ -52,9 +52,9 @@
 // messages. To send a message, call SendMessage(), and to listen for received
 // messages, implement the OnMessageReceived() callback.
 //
-// Note that a single device can be registered for multiple connection reasons.
-// If a device is registered for more than one reason, its connections (and
-// connection attempts) will remain active until all connection reasons have
+// Note that a single device can be registered for multiple connection requests.
+// If a device is registered for more than one request, its connections (and
+// connection attempts) will remain active until all connection requests have
 // been unregistered for the device.
 class BleConnectionManager : public BleScanner::Observer {
  public:
@@ -104,22 +104,22 @@
       scoped_refptr<device::BluetoothAdapter> adapter,
       BleAdvertisementDeviceQueue* ble_advertisement_device_queue,
       BleAdvertiser* ble_advertiser,
-      BleScanner* ble_scanner,
-      AdHocBleAdvertiser* ad_hoc_ble_advertisement);
+      BleScanner* ble_scanner);
   virtual ~BleConnectionManager();
 
-  // Registers |device_id| for |connection_reason|. Once registered, this
+  // Registers |device_id| for |request_id|. Once registered, this
   // instance will continue to attempt to connect and authenticate to that
   // device until the device is unregistered.
-  virtual void RegisterRemoteDevice(const std::string& device_id,
-                                    const ConnectionReason& connection_reason);
-
-  // Unregisters |device_id| for |connection_reason|. Once registered, a device
-  // will continue trying to connect until *ALL* of its ConnectionReasons have
-  // been unregistered.
-  virtual void UnregisterRemoteDevice(
+  virtual void RegisterRemoteDevice(
       const std::string& device_id,
-      const ConnectionReason& connection_reason);
+      const base::UnguessableToken& request_id,
+      secure_channel::ConnectionPriority connection_priority);
+
+  // Unregisters |device_id| for |request_id|. Once registered, a device
+  // will continue trying to connect until *ALL* of its connection reasons have
+  // been unregistered.
+  virtual void UnregisterRemoteDevice(const std::string& device_id,
+                                      const base::UnguessableToken& request_id);
 
   // Sends |message| to the device with ID |device_id|. This function can only
   // be called if the given device is authenticated. This function returns a
@@ -177,10 +177,12 @@
                        base::WeakPtr<BleConnectionManager> manager);
     ~ConnectionMetadata();
 
-    void RegisterConnectionReason(const ConnectionReason& connection_reason);
-    void UnregisterConnectionReason(const ConnectionReason& connection_reason);
-    ConnectionPriority GetConnectionPriority();
-    bool HasReasonForConnection() const;
+    void RegisterConnectionRequest(
+        const base::UnguessableToken& request_id,
+        secure_channel::ConnectionPriority connection_priority);
+    void UnregisterConnectionRequest(const base::UnguessableToken& request_id);
+    secure_channel::ConnectionPriority GetConnectionPriority();
+    bool HasPendingConnectionRequests() const;
 
     bool HasEstablishedConnection() const;
     cryptauth::SecureChannel::Status GetStatus() const;
@@ -203,7 +205,6 @@
                            const std::string& payload) override;
     void OnMessageSent(cryptauth::SecureChannel* secure_channel,
                        int sequence_number) override;
-    void OnGattCharacteristicsNotAvailable() override;
 
    private:
     friend class BleConnectionManagerTest;
@@ -211,7 +212,10 @@
     void OnConnectionAttemptTimeout();
 
     std::string device_id_;
-    std::set<ConnectionReason> active_connection_reasons_;
+    std::unordered_map<base::UnguessableToken,
+                       secure_channel::ConnectionPriority,
+                       base::UnguessableTokenHash>
+        request_id_to_priority_map_;
     std::unique_ptr<cryptauth::SecureChannel> secure_channel_;
     std::unique_ptr<base::Timer> connection_attempt_timeout_timer_;
     base::WeakPtr<BleConnectionManager> manager_;
@@ -236,7 +240,6 @@
       const cryptauth::SecureChannel::Status& old_status,
       const cryptauth::SecureChannel::Status& new_status,
       StateChangeDetail state_change_detail);
-  void OnGattCharacteristicsNotAvailable(const std::string& device_id);
 
   void SetTestTimerFactoryForTesting(
       std::unique_ptr<TimerFactory> test_timer_factory);
@@ -251,7 +254,6 @@
   BleAdvertisementDeviceQueue* ble_advertisement_device_queue_;
   BleAdvertiser* ble_advertiser_;
   BleScanner* ble_scanner_;
-  AdHocBleAdvertiser* ad_hoc_ble_advertisement_;
 
   std::unique_ptr<TimerFactory> timer_factory_;
 
diff --git a/chromeos/components/tether/ble_connection_manager_unittest.cc b/chromeos/components/tether/ble_connection_manager_unittest.cc
index 1e324a8..6387b33 100644
--- a/chromeos/components/tether/ble_connection_manager_unittest.cc
+++ b/chromeos/components/tether/ble_connection_manager_unittest.cc
@@ -6,9 +6,8 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/timer/mock_timer.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/tether/ble_constants.h"
-#include "chromeos/components/tether/connection_reason.h"
-#include "chromeos/components/tether/fake_ad_hoc_ble_advertiser.h"
 #include "chromeos/components/tether/fake_ble_advertiser.h"
 #include "chromeos/components/tether/fake_ble_scanner.h"
 #include "chromeos/components/tether/proto/tether.pb.h"
@@ -121,8 +120,8 @@
 class UnregisteringObserver : public BleConnectionManager::Observer {
  public:
   UnregisteringObserver(BleConnectionManager* manager,
-                        ConnectionReason connection_reason)
-      : manager_(manager), connection_reason_(connection_reason) {}
+                        const base::UnguessableToken& request_id)
+      : manager_(manager), request_id_(request_id) {}
 
   // BleConnectionManager::Observer:
   void OnSecureChannelStatusChanged(
@@ -130,19 +129,19 @@
       const cryptauth::SecureChannel::Status& old_status,
       const cryptauth::SecureChannel::Status& new_status,
       BleConnectionManager::StateChangeDetail status_change_detail) override {
-    manager_->UnregisterRemoteDevice(device_id, connection_reason_);
+    manager_->UnregisterRemoteDevice(device_id, request_id_);
   }
 
   void OnMessageReceived(const std::string& device_id,
                          const std::string& payload) override {
-    manager_->UnregisterRemoteDevice(device_id, connection_reason_);
+    manager_->UnregisterRemoteDevice(device_id, request_id_);
   }
 
   void OnMessageSent(int sequence_number) override { NOTIMPLEMENTED(); }
 
  private:
   BleConnectionManager* manager_;
-  ConnectionReason connection_reason_;
+  const base::UnguessableToken request_id_;
 };
 
 class TestMetricsObserver final : public BleConnectionManager::MetricsObserver {
@@ -358,8 +357,6 @@
     fake_ble_scanner_ = std::make_unique<FakeBleScanner>(
         true /* automatically_update_discovery_session */);
 
-    fake_ad_hoc_ble_advertiser_ = std::make_unique<FakeAdHocBleAdvertiser>();
-
     fake_connection_factory_ = base::WrapUnique(new FakeConnectionFactory(
         mock_adapter_, device::BluetoothUUID(kGattServerUuid)));
     cryptauth::weave::BluetoothLowEnergyWeaveClientConnection::Factory::
@@ -372,7 +369,7 @@
 
     manager_ = base::WrapUnique(new BleConnectionManager(
         mock_adapter_, device_queue_.get(), fake_ble_advertiser_.get(),
-        fake_ble_scanner_.get(), fake_ad_hoc_ble_advertiser_.get()));
+        fake_ble_scanner_.get()));
     test_observer_ = base::WrapUnique(new TestObserver());
     manager_->AddObserver(test_observer_.get());
     test_metrics_observer_ = base::WrapUnique(new TestMetricsObserver());
@@ -528,7 +525,7 @@
     BleConnectionManager::ConnectionMetadata* connection_metadata =
         manager_->GetConnectionMetadata(remote_device.GetDeviceId());
     if (connection_metadata)
-      EXPECT_FALSE(connection_metadata->HasReasonForConnection());
+      EXPECT_FALSE(connection_metadata->HasPendingConnectionRequests());
   }
 
   // Registers |remote_device|, creates a connection to that device at
@@ -536,10 +533,10 @@
   FakeSecureChannel* ConnectSuccessfully(
       cryptauth::RemoteDeviceRef remote_device,
       const std::string& bluetooth_address,
-      const ConnectionReason connection_reason,
+      const base::UnguessableToken& request_id,
       bool is_background_advertisement) {
-    manager_->RegisterRemoteDevice(remote_device.GetDeviceId(),
-                                   connection_reason);
+    manager_->RegisterRemoteDevice(remote_device.GetDeviceId(), request_id,
+                                   secure_channel::ConnectionPriority::kLow);
     VerifyAdvertisingTimeoutSet(remote_device);
     VerifyConnectionStateChanges(
         std::vector<SecureChannelStatusChange>{
@@ -632,7 +629,6 @@
   scoped_refptr<NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
   std::unique_ptr<FakeBleAdvertiser> fake_ble_advertiser_;
   std::unique_ptr<FakeBleScanner> fake_ble_scanner_;
-  std::unique_ptr<FakeAdHocBleAdvertiser> fake_ad_hoc_ble_advertiser_;
   std::unique_ptr<BleAdvertisementDeviceQueue> device_queue_;
   MockTimerFactory* mock_timer_factory_;
   std::unique_ptr<FakeConnectionFactory> fake_connection_factory_;
@@ -656,7 +652,8 @@
   fake_ble_scanner_->set_should_fail_to_register(true);
 
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -669,7 +666,8 @@
   fake_ble_advertiser_->set_should_fail_to_start_advertising(true);
 
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -680,7 +678,8 @@
 
 TEST_F(BleConnectionManagerTest, TestRegistersButNoResult) {
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -690,8 +689,9 @@
 }
 
 TEST_F(BleConnectionManagerTest, TestRegistersAndUnregister_NoConnection) {
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  base::UnguessableToken request_id = base::UnguessableToken::Create();
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -699,9 +699,7 @@
        cryptauth::SecureChannel::Status::CONNECTING,
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::CONNECTING,
@@ -710,41 +708,10 @@
            STATE_CHANGE_DETAIL_DEVICE_WAS_UNREGISTERED}});
 }
 
-TEST_F(BleConnectionManagerTest, TestAdHocBleAdvertiser) {
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
-  VerifyAdvertisingTimeoutSet(test_devices_[0]);
-  VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
-      {test_devices_[0].GetDeviceId(),
-       cryptauth::SecureChannel::Status::DISCONNECTED,
-       cryptauth::SecureChannel::Status::CONNECTING,
-       BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
-
-  // Simulate the channel failing to find GATT services and disconnecting.
-  FakeSecureChannel* channel = ReceiveAdvertisementAndConnectChannel(
-      test_devices_[0], kBluetoothAddress1,
-      false /* is_background_advertisement */);
-  channel->NotifyGattCharacteristicsNotAvailable();
-  channel->Disconnect();
-  VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
-      {test_devices_[0].GetDeviceId(),
-       cryptauth::SecureChannel::Status::CONNECTING,
-       cryptauth::SecureChannel::Status::DISCONNECTED,
-       BleConnectionManager::StateChangeDetail::
-           STATE_CHANGE_DETAIL_GATT_CONNECTION_WAS_ATTEMPTED},
-      {test_devices_[0].GetDeviceId(),
-       cryptauth::SecureChannel::Status::DISCONNECTED,
-       cryptauth::SecureChannel::Status::CONNECTING,
-       BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
-
-  // A GATT services workaround should have been requested for that device.
-  EXPECT_EQ(std::vector<std::string>{test_devices_[0].GetDeviceId()},
-            fake_ad_hoc_ble_advertiser_->requested_device_ids());
-}
-
 TEST_F(BleConnectionManagerTest, TestRegisterWithNoConnection_TimeoutOccurs) {
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  base::UnguessableToken request_id = base::UnguessableToken::Create();
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -764,9 +731,7 @@
        cryptauth::SecureChannel::Status::CONNECTING,
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::CONNECTING,
@@ -777,7 +742,8 @@
 
 TEST_F(BleConnectionManagerTest, TestSuccessfulConnection_FailsAuthentication) {
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -824,9 +790,9 @@
 }
 
 TEST_F(BleConnectionManagerTest, TestSuccessfulConnection_SendAndReceive) {
+  base::UnguessableToken request_id = base::UnguessableToken::Create();
   FakeSecureChannel* channel =
-      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                          ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1, request_id,
                           false /* is_background_advertisement */);
 
   int sequence_number =
@@ -845,9 +811,7 @@
   VerifyReceivedMessages(std::vector<ReceivedMessage>{
       {test_devices_[0].GetDeviceId(), "response2"}});
 
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -868,7 +832,7 @@
 TEST_F(BleConnectionManagerTest,
        TestSuccessfulConnection_BackgroundAdvertisement) {
   ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                      ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+                      base::UnguessableToken::Create(),
                       true /* is_background_advertisement */);
   used_background_advertisements_ = true;
 }
@@ -877,7 +841,8 @@
 TEST_F(BleConnectionManagerTest,
        TestSuccessfulConnection_MultipleAdvertisementsReceived) {
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -908,25 +873,26 @@
 
 TEST_F(BleConnectionManagerTest,
        TestSuccessfulConnection_MultipleConnectionReasons) {
-  ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                      ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+  base::UnguessableToken request_id_1 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_2 = base::UnguessableToken::Create();
+
+  ConnectSuccessfully(test_devices_[0], kBluetoothAddress1, request_id_1,
                       false /* is_background_advertisement */);
 
   // Now, register a different connection reason.
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::CONNECT_TETHERING_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id_2,
+                                 secure_channel::ConnectionPriority::kLow);
 
   // Unregister the |TETHER_AVAILABILITY_REQUEST| reason, but leave the
   // |CONNECT_TETHERING_REQUEST| registered.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
+                                   request_id_1);
   VerifyDeviceRegistered(test_devices_[0]);
 
   // Now, unregister the other reason; this should cause the device to be
   // fully unregistered.
   manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                   ConnectionReason::CONNECT_TETHERING_REQUEST);
+                                   request_id_2);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -945,14 +911,15 @@
 }
 
 TEST_F(BleConnectionManagerTest, TestGetStatusForDevice) {
+  base::UnguessableToken request_id = base::UnguessableToken::Create();
   cryptauth::SecureChannel::Status status;
 
   // Should return false when the device has not yet been registered at all.
   EXPECT_FALSE(
       manager_->GetStatusForDevice(test_devices_[0].GetDeviceId(), &status));
 
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -1007,9 +974,7 @@
   EXPECT_EQ(cryptauth::SecureChannel::Status::AUTHENTICATED, status);
 
   // Now, unregister the device.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -1033,10 +998,9 @@
 
 TEST_F(BleConnectionManagerTest,
        TestSuccessfulConnection_DisconnectsAfterConnection) {
-  FakeSecureChannel* channel =
-      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                          ConnectionReason::TETHER_AVAILABILITY_REQUEST,
-                          false /* is_background_advertisement */);
+  FakeSecureChannel* channel = ConnectSuccessfully(
+      test_devices_[0], kBluetoothAddress1, base::UnguessableToken::Create(),
+      false /* is_background_advertisement */);
 
   channel->ChangeStatus(cryptauth::SecureChannel::Status::DISCONNECTED);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
@@ -1054,7 +1018,8 @@
 TEST_F(BleConnectionManagerTest, TwoDevices_NeitherCanScan) {
   fake_ble_scanner_->set_should_fail_to_register(true);
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -1063,7 +1028,8 @@
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
   manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[1]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
@@ -1076,7 +1042,8 @@
   fake_ble_advertiser_->set_should_fail_to_start_advertising(true);
 
   manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -1085,7 +1052,8 @@
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
   manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+                                 base::UnguessableToken::Create(),
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyFailImmediatelyTimeoutSet(test_devices_[1]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
@@ -1096,9 +1064,12 @@
 
 TEST_F(BleConnectionManagerTest,
        TwoDevices_RegisterWithNoConnection_TimerFires) {
+  base::UnguessableToken request_id_1 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_2 = base::UnguessableToken::Create();
+
   // Register device 0.
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id_1,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -1107,8 +1078,8 @@
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
   // Register device 1.
-  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(), request_id_2,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[1]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
@@ -1143,9 +1114,8 @@
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
   // Unregister device 0.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
+                                   request_id_1);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::CONNECTING,
@@ -1154,9 +1124,8 @@
            STATE_CHANGE_DETAIL_DEVICE_WAS_UNREGISTERED}});
 
   // Unregister device 1.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[1].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[1].GetDeviceId(),
+                                   request_id_2);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
        cryptauth::SecureChannel::Status::CONNECTING,
@@ -1166,14 +1135,16 @@
 }
 
 TEST_F(BleConnectionManagerTest, TwoDevices_OneConnects) {
+  base::UnguessableToken request_id_1 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_2 = base::UnguessableToken::Create();
+
   // Successfully connect to device 0.
-  ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                      ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+  ConnectSuccessfully(test_devices_[0], kBluetoothAddress1, request_id_1,
                       false /* is_background_advertisement */);
 
   // Register device 1.
-  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(), request_id_2,
+                                 secure_channel::ConnectionPriority::kLow);
   VerifyAdvertisingTimeoutSet(test_devices_[1]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
@@ -1195,9 +1166,8 @@
        BleConnectionManager::StateChangeDetail::STATE_CHANGE_DETAIL_NONE}});
 
   // Unregister device 0.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
+                                   request_id_1);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -1214,9 +1184,8 @@
            STATE_CHANGE_DETAIL_DEVICE_WAS_UNREGISTERED}});
 
   // Unregister device 1.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[1].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[1].GetDeviceId(),
+                                   request_id_2);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
        cryptauth::SecureChannel::Status::CONNECTING,
@@ -1226,14 +1195,15 @@
 }
 
 TEST_F(BleConnectionManagerTest, TwoDevices_BothConnectSendAndReceive) {
+  base::UnguessableToken request_id_1 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_2 = base::UnguessableToken::Create();
+
   FakeSecureChannel* channel0 =
-      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                          ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1, request_id_1,
                           false /* is_background_advertisement */);
 
   FakeSecureChannel* channel1 =
-      ConnectSuccessfully(test_devices_[1], kBluetoothAddress2,
-                          ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+      ConnectSuccessfully(test_devices_[1], kBluetoothAddress2, request_id_2,
                           false /* is_background_advertisement */);
 
   int sequence_number =
@@ -1268,9 +1238,8 @@
   VerifyReceivedMessages(std::vector<ReceivedMessage>{
       {test_devices_[1].GetDeviceId(), "response2_device1"}});
 
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
+                                   request_id_1);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -1287,9 +1256,8 @@
        BleConnectionManager::StateChangeDetail::
            STATE_CHANGE_DETAIL_DEVICE_WAS_UNREGISTERED}});
 
-  manager_->UnregisterRemoteDevice(
-      test_devices_[1].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[1].GetDeviceId(),
+                                   request_id_2);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[1].GetDeviceId(),
        cryptauth::SecureChannel::Status::AUTHENTICATED,
@@ -1308,16 +1276,21 @@
 }
 
 TEST_F(BleConnectionManagerTest, FourDevices_ComprehensiveTest) {
+  base::UnguessableToken request_id_1 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_2 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_3 = base::UnguessableToken::Create();
+  base::UnguessableToken request_id_4 = base::UnguessableToken::Create();
+
   // Register all devices. Since the maximum number of simultaneous connection
   // attempts is 2, only devices 0 and 1 should actually start connecting.
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
-  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
-  manager_->RegisterRemoteDevice(test_devices_[2].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
-  manager_->RegisterRemoteDevice(test_devices_[3].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id_1,
+                                 secure_channel::ConnectionPriority::kLow);
+  manager_->RegisterRemoteDevice(test_devices_[1].GetDeviceId(), request_id_2,
+                                 secure_channel::ConnectionPriority::kLow);
+  manager_->RegisterRemoteDevice(test_devices_[2].GetDeviceId(), request_id_3,
+                                 secure_channel::ConnectionPriority::kLow);
+  manager_->RegisterRemoteDevice(test_devices_[3].GetDeviceId(), request_id_4,
+                                 secure_channel::ConnectionPriority::kLow);
 
   // Devices 0 and 1 should be advertising; devices 2 and 3 should not be.
   VerifyAdvertisingTimeoutSet(test_devices_[0]);
@@ -1375,9 +1348,8 @@
       {test_devices_[0].GetDeviceId(), "response1"}});
 
   // Now, device 0 is unregistered.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[0].GetDeviceId(),
+                                   request_id_1);
   VerifyDeviceNotRegistered(test_devices_[0]);
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
       {test_devices_[0].GetDeviceId(),
@@ -1433,17 +1405,14 @@
 
   // Assume that none of the other devices can connect, and unregister the
   // remaining 3 devices.
-  manager_->UnregisterRemoteDevice(
-      test_devices_[3].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[3].GetDeviceId(),
+                                   request_id_4);
   VerifyDeviceNotRegistered(test_devices_[3]);
-  manager_->UnregisterRemoteDevice(
-      test_devices_[1].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[1].GetDeviceId(),
+                                   request_id_2);
   VerifyDeviceNotRegistered(test_devices_[1]);
-  manager_->UnregisterRemoteDevice(
-      test_devices_[2].GetDeviceId(),
-      ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->UnregisterRemoteDevice(test_devices_[2].GetDeviceId(),
+                                   request_id_3);
   VerifyDeviceNotRegistered(test_devices_[2]);
 
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
@@ -1484,17 +1453,16 @@
 // pass the parameters from the ConnectionMetadata to BleConnectionManager by
 // value instead.
 TEST_F(BleConnectionManagerTest, ObserverUnregisters) {
+  base::UnguessableToken request_id = base::UnguessableToken::Create();
+
   FakeSecureChannel* channel =
-      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1,
-                          ConnectionReason::TETHER_AVAILABILITY_REQUEST,
+      ConnectSuccessfully(test_devices_[0], kBluetoothAddress1, request_id,
                           false /* is_background_advertisement */);
 
   // Register two separate UnregisteringObservers. When a message is received,
   // the first observer will unregister the device.
-  UnregisteringObserver first(manager_.get(),
-                              ConnectionReason::TETHER_AVAILABILITY_REQUEST);
-  UnregisteringObserver second(manager_.get(),
-                               ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  UnregisteringObserver first(manager_.get(), request_id);
+  UnregisteringObserver second(manager_.get(), request_id);
   manager_->AddObserver(&first);
   manager_->AddObserver(&second);
 
@@ -1525,8 +1493,8 @@
   // connecting" status change. This time, the multiple observers will respond
   // to a status change event instead of a message received event. This also
   // would have caused a crash before the fix for crbug.com/733360.
-  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(),
-                                 ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+  manager_->RegisterRemoteDevice(test_devices_[0].GetDeviceId(), request_id,
+                                 secure_channel::ConnectionPriority::kLow);
 
   // We expect the device to be unregistered (by the observer).
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
diff --git a/chromeos/components/tether/connect_tethering_operation.cc b/chromeos/components/tether/connect_tethering_operation.cc
index 86c1dc16..fe39a00 100644
--- a/chromeos/components/tether/connect_tethering_operation.cc
+++ b/chromeos/components/tether/connect_tethering_operation.cc
@@ -73,6 +73,7 @@
     bool setup_required)
     : MessageTransferOperation(
           cryptauth::RemoteDeviceRefList{device_to_connect},
+          secure_channel::ConnectionPriority::kHigh,
           connection_manager),
       remote_device_(device_to_connect),
       tether_host_response_recorder_(tether_host_response_recorder),
diff --git a/chromeos/components/tether/connection_preserver_impl.cc b/chromeos/components/tether/connection_preserver_impl.cc
index 025c2008..0c9a66a2 100644
--- a/chromeos/components/tether/connection_preserver_impl.cc
+++ b/chromeos/components/tether/connection_preserver_impl.cc
@@ -26,6 +26,7 @@
       network_state_handler_(network_state_handler),
       active_host_(active_host),
       tether_host_response_recorder_(tether_host_response_recorder),
+      request_id_(base::UnguessableToken::Create()),
       preserved_connection_timer_(std::make_unique<base::OneShotTimer>()),
       weak_ptr_factory_(this) {
   active_host_->AddObserver(this);
@@ -124,7 +125,7 @@
 
   preserved_connection_device_id_ = device_id;
   ble_connection_manager_->RegisterRemoteDevice(
-      device_id, ConnectionReason::PRESERVE_CONNECTION);
+      device_id, request_id_, secure_channel::ConnectionPriority::kLow);
 
   preserved_connection_timer_->Start(
       FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutSeconds),
@@ -142,7 +143,7 @@
                << ".";
 
   ble_connection_manager_->UnregisterRemoteDevice(
-      preserved_connection_device_id_, ConnectionReason::PRESERVE_CONNECTION);
+      preserved_connection_device_id_, request_id_);
   preserved_connection_device_id_.clear();
   preserved_connection_timer_->Stop();
 }
diff --git a/chromeos/components/tether/connection_preserver_impl.h b/chromeos/components/tether/connection_preserver_impl.h
index c6acaf8f..9d4d4c7 100644
--- a/chromeos/components/tether/connection_preserver_impl.h
+++ b/chromeos/components/tether/connection_preserver_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/timer/timer.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/tether/active_host.h"
 #include "chromeos/components/tether/connection_preserver.h"
 
@@ -63,6 +64,8 @@
   NetworkStateHandler* network_state_handler_;
   ActiveHost* active_host_;
   TetherHostResponseRecorder* tether_host_response_recorder_;
+  const base::UnguessableToken request_id_;
+
   std::unique_ptr<base::Timer> preserved_connection_timer_;
 
   std::string preserved_connection_device_id_;
@@ -76,4 +79,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROMEOS_COMPONENTS_TETHER_CONNECTION_PRESERVER_IMPL_H_
\ No newline at end of file
+#endif  // CHROMEOS_COMPONENTS_TETHER_CONNECTION_PRESERVER_IMPL_H_
diff --git a/chromeos/components/tether/connection_preserver_impl_unittest.cc b/chromeos/components/tether/connection_preserver_impl_unittest.cc
index b2ac85b..90e42570 100644
--- a/chromeos/components/tether/connection_preserver_impl_unittest.cc
+++ b/chromeos/components/tether/connection_preserver_impl_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/timer/mock_timer.h"
-#include "chromeos/components/tether/connection_reason.h"
 #include "chromeos/components/tether/fake_active_host.h"
 #include "chromeos/components/tether/fake_ble_connection_manager.h"
 #include "chromeos/components/tether/mock_tether_host_response_recorder.h"
@@ -19,6 +18,7 @@
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -90,16 +90,16 @@
 
   void SimulateSuccessfulHostScan(const std::string& device_id,
                                   bool should_remain_registered) {
+    base::UnguessableToken request_id = base::UnguessableToken::Create();
     fake_ble_connection_manager_->RegisterRemoteDevice(
-        device_id, ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+        device_id, request_id, secure_channel::ConnectionPriority::kLow);
     EXPECT_TRUE(fake_ble_connection_manager_->IsRegistered(device_id));
 
     connection_preserver_->HandleSuccessfulTetherAvailabilityResponse(
         device_id);
     EXPECT_TRUE(fake_ble_connection_manager_->IsRegistered(device_id));
 
-    fake_ble_connection_manager_->UnregisterRemoteDevice(
-        device_id, ConnectionReason::TETHER_AVAILABILITY_REQUEST);
+    fake_ble_connection_manager_->UnregisterRemoteDevice(device_id, request_id);
     EXPECT_EQ(should_remain_registered,
               fake_ble_connection_manager_->IsRegistered(device_id));
   }
diff --git a/chromeos/components/tether/connection_priority.cc b/chromeos/components/tether/connection_priority.cc
deleted file mode 100644
index f020f2cb..0000000
--- a/chromeos/components/tether/connection_priority.cc
+++ /dev/null
@@ -1,44 +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.
-
-#include "chromeos/components/tether/connection_priority.h"
-
-#include "base/logging.h"
-
-namespace chromeos {
-
-namespace tether {
-
-ConnectionPriority PriorityForConnectionReason(
-    ConnectionReason connection_reason) {
-  switch (connection_reason) {
-    case ConnectionReason::CONNECT_TETHERING_REQUEST:
-    case ConnectionReason::DISCONNECT_TETHERING_REQUEST:
-      return ConnectionPriority::CONNECTION_PRIORITY_HIGH;
-    case ConnectionReason::KEEP_ALIVE_TICKLE:
-      return ConnectionPriority::CONNECTION_PRIORITY_MEDIUM;
-    default:
-      return ConnectionPriority::CONNECTION_PRIORITY_LOW;
-  }
-}
-
-ConnectionPriority HighestPriorityForConnectionReasons(
-    std::set<ConnectionReason> connection_reasons) {
-  DCHECK(!connection_reasons.empty());
-
-  ConnectionPriority highest_priority =
-      ConnectionPriority::CONNECTION_PRIORITY_LOW;
-  for (const auto& connection_reason : connection_reasons) {
-    ConnectionPriority priority_for_type =
-        PriorityForConnectionReason(connection_reason);
-    if (priority_for_type > highest_priority)
-      highest_priority = priority_for_type;
-  }
-
-  return highest_priority;
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/connection_priority.h b/chromeos/components/tether/connection_priority.h
deleted file mode 100644
index 8832227..0000000
--- a/chromeos/components/tether/connection_priority.h
+++ /dev/null
@@ -1,46 +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.
-
-#ifndef CHROMEOS_COMPONENTS_TETHER_CONNECTION_PRIORITY_H_
-#define CHROMEOS_COMPONENTS_TETHER_CONNECTION_PRIORITY_H_
-
-#include <set>
-
-#include "chromeos/components/tether/connection_reason.h"
-#include "chromeos/components/tether/proto/tether.pb.h"
-
-namespace chromeos {
-
-namespace tether {
-
-// Priority of connections; lower-priority connections will remain queued until
-// higher-priority connections are complete.
-enum class ConnectionPriority {
-  // Low-priority applies to background connections which were not triggered by
-  // the user and do not have a strict latency requirement.
-  //   Example: TetherAvailabilityRequest messages.
-  CONNECTION_PRIORITY_LOW = 1,
-
-  // Medium-priority applies to connections which should finish within a
-  // reasonable amount of time but whose latency is not directly observed by
-  // the user.
-  //   Example: KeepAliveTickle messages.
-  CONNECTION_PRIORITY_MEDIUM = 2,
-
-  // High-priority applies to connections whose latency is directly observable
-  // by the user, usually as part of the UI.
-  //   Example: ConnectTetheringRequest and DisconnectTetheringRequest messages.
-  CONNECTION_PRIORITY_HIGH = 3
-};
-
-ConnectionPriority PriorityForConnectionReason(
-    ConnectionReason connection_reason);
-ConnectionPriority HighestPriorityForConnectionReasons(
-    std::set<ConnectionReason> connection_reasons);
-
-}  // namespace tether
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_COMPONENTS_TETHER_CONNECTION_PRIORITY_H_
diff --git a/chromeos/components/tether/connection_reason.cc b/chromeos/components/tether/connection_reason.cc
deleted file mode 100644
index d5550bde..0000000
--- a/chromeos/components/tether/connection_reason.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/components/tether/connection_reason.h"
-
-#include "base/logging.h"
-
-namespace chromeos {
-
-namespace tether {
-
-ConnectionReason MessageTypeToConnectionReason(const MessageType& reason) {
-  switch (reason) {
-    case MessageType::TETHER_AVAILABILITY_REQUEST:
-      return ConnectionReason::TETHER_AVAILABILITY_REQUEST;
-    case MessageType::CONNECT_TETHERING_REQUEST:
-      return ConnectionReason::CONNECT_TETHERING_REQUEST;
-    case MessageType::KEEP_ALIVE_TICKLE:
-      return ConnectionReason::KEEP_ALIVE_TICKLE;
-    case MessageType::DISCONNECT_TETHERING_REQUEST:
-      return ConnectionReason::DISCONNECT_TETHERING_REQUEST;
-    default:
-      // A MessageType that doesn't correspond to a valid request message was
-      // passed.
-      NOTREACHED();
-      return ConnectionReason::TETHER_AVAILABILITY_REQUEST;
-  }
-}
-
-std::string ConnectionReasonToString(const ConnectionReason& reason) {
-  switch (reason) {
-    case ConnectionReason::TETHER_AVAILABILITY_REQUEST:
-      return "[TetherAvailabilityRequest]";
-    case ConnectionReason::CONNECT_TETHERING_REQUEST:
-      return "[ConnectTetheringRequest]";
-    case ConnectionReason::KEEP_ALIVE_TICKLE:
-      return "[KeepAliveTickle]";
-    case ConnectionReason::DISCONNECT_TETHERING_REQUEST:
-      return "[DisconnectTetheringRequest]";
-    case ConnectionReason::PRESERVE_CONNECTION:
-      return "[PreserveConnection]";
-    default:
-      return "[invalid ConnectionReason]";
-  }
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/connection_reason.h b/chromeos/components/tether/connection_reason.h
deleted file mode 100644
index c949e6f..0000000
--- a/chromeos/components/tether/connection_reason.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_COMPONENTS_TETHER_CONNECTION_REASON_H_
-#define CHROMEOS_COMPONENTS_TETHER_CONNECTION_REASON_H_
-
-#include <set>
-
-#include "chromeos/components/tether/proto/tether.pb.h"
-
-namespace chromeos {
-
-namespace tether {
-
-// Describes why a connection should be made to remote device. Most directly
-// correspond to which MessageType should be sent to that same device.
-enum class ConnectionReason {
-  TETHER_AVAILABILITY_REQUEST,
-  CONNECT_TETHERING_REQUEST,
-  KEEP_ALIVE_TICKLE,
-  DISCONNECT_TETHERING_REQUEST,
-  // Indicates a connection should live beyond its immediately useful lifetime;
-  // does not indicate that a particular message should be sent.
-  PRESERVE_CONNECTION
-};
-
-ConnectionReason MessageTypeToConnectionReason(const MessageType& reason);
-std::string ConnectionReasonToString(const ConnectionReason& reason);
-
-}  // namespace tether
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_COMPONENTS_TETHER_CONNECTION_REASON_H_
diff --git a/chromeos/components/tether/disconnect_tethering_operation.cc b/chromeos/components/tether/disconnect_tethering_operation.cc
index a581818..7d272efb 100644
--- a/chromeos/components/tether/disconnect_tethering_operation.cc
+++ b/chromeos/components/tether/disconnect_tethering_operation.cc
@@ -52,6 +52,7 @@
     BleConnectionManager* connection_manager)
     : MessageTransferOperation(
           cryptauth::RemoteDeviceRefList{device_to_connect},
+          secure_channel::ConnectionPriority::kHigh,
           connection_manager),
       remote_device_(device_to_connect),
       has_sent_message_(false),
diff --git a/chromeos/components/tether/fake_ad_hoc_ble_advertiser.cc b/chromeos/components/tether/fake_ad_hoc_ble_advertiser.cc
deleted file mode 100644
index a37b3c454..0000000
--- a/chromeos/components/tether/fake_ad_hoc_ble_advertiser.cc
+++ /dev/null
@@ -1,30 +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.
-
-#include "chromeos/components/tether/fake_ad_hoc_ble_advertiser.h"
-
-namespace chromeos {
-
-namespace tether {
-
-FakeAdHocBleAdvertiser::FakeAdHocBleAdvertiser() {}
-
-FakeAdHocBleAdvertiser::~FakeAdHocBleAdvertiser() {}
-
-void FakeAdHocBleAdvertiser::NotifyAsynchronousShutdownComplete() {
-  AdHocBleAdvertiser::NotifyAsynchronousShutdownComplete();
-}
-
-void FakeAdHocBleAdvertiser::RequestGattServicesForDevice(
-    const std::string& device_id) {
-  requested_device_ids_.push_back(device_id);
-}
-
-bool FakeAdHocBleAdvertiser::HasPendingRequests() {
-  return has_pending_requests_;
-}
-
-}  // namespace tether
-
-}  // namespace chromeos
diff --git a/chromeos/components/tether/fake_ad_hoc_ble_advertiser.h b/chromeos/components/tether/fake_ad_hoc_ble_advertiser.h
deleted file mode 100644
index a6a2ad6..0000000
--- a/chromeos/components/tether/fake_ad_hoc_ble_advertiser.h
+++ /dev/null
@@ -1,49 +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.
-
-#ifndef CHROMEOS_COMPONENTS_TETHER_FAKE_AD_HOC_BLE_ADVERTISER_H_
-#define CHROMEOS_COMPONENTS_TETHER_FAKE_AD_HOC_BLE_ADVERTISER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "chromeos/components/tether/ad_hoc_ble_advertiser.h"
-
-namespace chromeos {
-
-namespace tether {
-
-// Test doublue for AdHocBleAdvertiser.
-class FakeAdHocBleAdvertiser : public AdHocBleAdvertiser {
- public:
-  FakeAdHocBleAdvertiser();
-  ~FakeAdHocBleAdvertiser() override;
-
-  void set_has_pending_requests(bool has_pending_requests) {
-    has_pending_requests_ = has_pending_requests;
-  }
-
-  const std::vector<std::string>& requested_device_ids() {
-    return requested_device_ids_;
-  }
-
-  void NotifyAsynchronousShutdownComplete();
-
-  // AdHocBleAdvertiser:
-  void RequestGattServicesForDevice(const std::string& device_id) override;
-  bool HasPendingRequests() override;
-
- private:
-  bool has_pending_requests_ = false;
-  std::vector<std::string> requested_device_ids_;
-
-  DISALLOW_COPY_AND_ASSIGN(FakeAdHocBleAdvertiser);
-};
-
-}  // namespace tether
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_COMPONENTS_TETHER_FAKE_AD_HOC_BLE_ADVERTISER_H_
diff --git a/chromeos/components/tether/fake_ble_connection_manager.cc b/chromeos/components/tether/fake_ble_connection_manager.cc
index 749688db..4367ad2 100644
--- a/chromeos/components/tether/fake_ble_connection_manager.cc
+++ b/chromeos/components/tether/fake_ble_connection_manager.cc
@@ -11,22 +11,21 @@
 
 namespace tether {
 
-FakeBleConnectionManager::StatusAndRegisteredConnectionReasons::
-    StatusAndRegisteredConnectionReasons()
+FakeBleConnectionManager::StatusAndRegisteredConnectionRequestIds::
+    StatusAndRegisteredConnectionRequestIds()
     : status(cryptauth::SecureChannel::Status::DISCONNECTED) {}
 
-FakeBleConnectionManager::StatusAndRegisteredConnectionReasons::
-    StatusAndRegisteredConnectionReasons(
-        const StatusAndRegisteredConnectionReasons& other) = default;
+FakeBleConnectionManager::StatusAndRegisteredConnectionRequestIds::
+    StatusAndRegisteredConnectionRequestIds(
+        const StatusAndRegisteredConnectionRequestIds& other) = default;
 
-FakeBleConnectionManager::StatusAndRegisteredConnectionReasons::
-    ~StatusAndRegisteredConnectionReasons() = default;
+FakeBleConnectionManager::StatusAndRegisteredConnectionRequestIds::
+    ~StatusAndRegisteredConnectionRequestIds() = default;
 
 FakeBleConnectionManager::FakeBleConnectionManager()
     : BleConnectionManager(nullptr,
                            nullptr,
                            nullptr,
-                           nullptr,
                            nullptr) {}
 
 FakeBleConnectionManager::~FakeBleConnectionManager() = default;
@@ -94,17 +93,18 @@
 
 void FakeBleConnectionManager::RegisterRemoteDevice(
     const std::string& device_id,
-    const ConnectionReason& connection_reason) {
-  StatusAndRegisteredConnectionReasons& value = device_id_map_[device_id];
-  value.registered_message_types.insert(connection_reason);
+    const base::UnguessableToken& request_id,
+    secure_channel::ConnectionPriority connection_priority) {
+  StatusAndRegisteredConnectionRequestIds& value = device_id_map_[device_id];
+  value.registered_request_ids.insert(request_id);
 }
 
 void FakeBleConnectionManager::UnregisterRemoteDevice(
     const std::string& device_id,
-    const ConnectionReason& connection_reason) {
-  StatusAndRegisteredConnectionReasons& value = device_id_map_[device_id];
-  value.registered_message_types.erase(connection_reason);
-  if (value.registered_message_types.empty())
+    const base::UnguessableToken& request_id) {
+  StatusAndRegisteredConnectionRequestIds& value = device_id_map_[device_id];
+  value.registered_request_ids.erase(request_id);
+  if (value.registered_request_ids.empty())
     device_id_map_.erase(device_id);
 }
 
diff --git a/chromeos/components/tether/fake_ble_connection_manager.h b/chromeos/components/tether/fake_ble_connection_manager.h
index 19160e7d..ed04074 100644
--- a/chromeos/components/tether/fake_ble_connection_manager.h
+++ b/chromeos/components/tether/fake_ble_connection_manager.h
@@ -9,7 +9,9 @@
 #include <set>
 
 #include "base/macros.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/tether/ble_connection_manager.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -54,11 +56,13 @@
   bool IsRegistered(const std::string& device_id);
 
   // BleConnectionManager:
-  void RegisterRemoteDevice(const std::string& device_id,
-                            const ConnectionReason& connection_reason) override;
+  void RegisterRemoteDevice(
+      const std::string& device_id,
+      const base::UnguessableToken& request_id,
+      secure_channel::ConnectionPriority connection_priority) override;
   void UnregisterRemoteDevice(
       const std::string& device_id,
-      const ConnectionReason& connection_reason) override;
+      const base::UnguessableToken& request_id) override;
   int SendMessage(const std::string& device_id,
                   const std::string& message) override;
   bool GetStatusForDevice(
@@ -68,18 +72,18 @@
   using BleConnectionManager::NotifyAdvertisementReceived;
 
  private:
-  struct StatusAndRegisteredConnectionReasons {
-    StatusAndRegisteredConnectionReasons();
-    StatusAndRegisteredConnectionReasons(
-        const StatusAndRegisteredConnectionReasons& other);
-    ~StatusAndRegisteredConnectionReasons();
+  struct StatusAndRegisteredConnectionRequestIds {
+    StatusAndRegisteredConnectionRequestIds();
+    StatusAndRegisteredConnectionRequestIds(
+        const StatusAndRegisteredConnectionRequestIds& other);
+    ~StatusAndRegisteredConnectionRequestIds();
 
     cryptauth::SecureChannel::Status status;
-    std::set<ConnectionReason> registered_message_types;
+    std::set<base::UnguessableToken> registered_request_ids;
   };
 
   int next_sequence_number_ = 0;
-  std::map<std::string, StatusAndRegisteredConnectionReasons> device_id_map_;
+  std::map<std::string, StatusAndRegisteredConnectionRequestIds> device_id_map_;
   std::vector<SentMessage> sent_messages_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeBleConnectionManager);
diff --git a/chromeos/components/tether/host_scanner_operation.cc b/chromeos/components/tether/host_scanner_operation.cc
index d6ad6ac..c99f7d4 100644
--- a/chromeos/components/tether/host_scanner_operation.cc
+++ b/chromeos/components/tether/host_scanner_operation.cc
@@ -138,6 +138,7 @@
     ConnectionPreserver* connection_preserver)
     : MessageTransferOperation(
           PrioritizeDevices(devices_to_connect, host_scan_device_prioritizer),
+          secure_channel::ConnectionPriority::kLow,
           connection_manager),
       tether_host_response_recorder_(tether_host_response_recorder),
       connection_preserver_(connection_preserver),
diff --git a/chromeos/components/tether/keep_alive_operation.cc b/chromeos/components/tether/keep_alive_operation.cc
index 97f90e4..966f764 100644
--- a/chromeos/components/tether/keep_alive_operation.cc
+++ b/chromeos/components/tether/keep_alive_operation.cc
@@ -47,6 +47,7 @@
     BleConnectionManager* connection_manager)
     : MessageTransferOperation(
           cryptauth::RemoteDeviceRefList{device_to_connect},
+          secure_channel::ConnectionPriority::kMedium,
           connection_manager),
       remote_device_(device_to_connect),
       clock_(base::DefaultClock::GetInstance()) {}
diff --git a/chromeos/components/tether/message_transfer_operation.cc b/chromeos/components/tether/message_transfer_operation.cc
index f652813..f874e29 100644
--- a/chromeos/components/tether/message_transfer_operation.cc
+++ b/chromeos/components/tether/message_transfer_operation.cc
@@ -8,7 +8,6 @@
 #include <set>
 
 #include "chromeos/components/proximity_auth/logging/logging.h"
-#include "chromeos/components/tether/connection_reason.h"
 #include "chromeos/components/tether/message_wrapper.h"
 #include "chromeos/components/tether/timer_factory.h"
 
@@ -44,9 +43,12 @@
 
 MessageTransferOperation::MessageTransferOperation(
     const cryptauth::RemoteDeviceRefList& devices_to_connect,
+    secure_channel::ConnectionPriority connection_priority,
     BleConnectionManager* connection_manager)
     : remote_devices_(RemoveDuplicatesFromVector(devices_to_connect)),
       connection_manager_(connection_manager),
+      connection_priority_(connection_priority),
+      request_id_(base::UnguessableToken::Create()),
       timer_factory_(std::make_unique<TimerFactory>()),
       weak_ptr_factory_(this) {}
 
@@ -86,8 +88,7 @@
 
   for (const auto& remote_device : remote_devices_) {
     connection_manager_->RegisterRemoteDevice(
-        remote_device.GetDeviceId(),
-        MessageTypeToConnectionReason(message_type_for_connection_));
+        remote_device.GetDeviceId(), request_id_, connection_priority_);
 
     cryptauth::SecureChannel::Status status;
     if (connection_manager_->GetStatusForDevice(remote_device.GetDeviceId(),
@@ -165,9 +166,8 @@
                         remote_devices_.end());
   StopTimerForDeviceIfRunning(remote_device_copy);
 
-  connection_manager_->UnregisterRemoteDevice(
-      remote_device_copy.GetDeviceId(),
-      MessageTypeToConnectionReason(message_type_for_connection_));
+  connection_manager_->UnregisterRemoteDevice(remote_device_copy.GetDeviceId(),
+                                              request_id_);
 
   if (!shutting_down_ && remote_devices_.empty())
     OnOperationFinished();
diff --git a/chromeos/components/tether/message_transfer_operation.h b/chromeos/components/tether/message_transfer_operation.h
index a3d80142..6847076 100644
--- a/chromeos/components/tether/message_transfer_operation.h
+++ b/chromeos/components/tether/message_transfer_operation.h
@@ -11,7 +11,9 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/timer/timer.h"
+#include "base/unguessable_token.h"
 #include "chromeos/components/tether/ble_connection_manager.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -41,6 +43,7 @@
 
   MessageTransferOperation(
       const cryptauth::RemoteDeviceRefList& devices_to_connect,
+      secure_channel::ConnectionPriority connection_priority,
       BleConnectionManager* connection_manager);
   virtual ~MessageTransferOperation();
 
@@ -130,6 +133,9 @@
 
   cryptauth::RemoteDeviceRefList remote_devices_;
   BleConnectionManager* connection_manager_;
+  const secure_channel::ConnectionPriority connection_priority_;
+  const base::UnguessableToken request_id_;
+
   std::unique_ptr<TimerFactory> timer_factory_;
 
   bool initialized_ = false;
diff --git a/chromeos/components/tether/message_transfer_operation_unittest.cc b/chromeos/components/tether/message_transfer_operation_unittest.cc
index 7bb98b6..eb022e8 100644
--- a/chromeos/components/tether/message_transfer_operation_unittest.cc
+++ b/chromeos/components/tether/message_transfer_operation_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/timer/mock_timer.h"
-#include "chromeos/components/tether/connection_reason.h"
 #include "chromeos/components/tether/fake_ble_connection_manager.h"
 #include "chromeos/components/tether/message_wrapper.h"
 #include "chromeos/components/tether/proto_test_util.h"
@@ -36,7 +35,9 @@
  public:
   TestOperation(const cryptauth::RemoteDeviceRefList& devices_to_connect,
                 BleConnectionManager* connection_manager)
-      : MessageTransferOperation(devices_to_connect, connection_manager) {}
+      : MessageTransferOperation(devices_to_connect,
+                                 secure_channel::ConnectionPriority::kLow,
+                                 connection_manager) {}
   ~TestOperation() override = default;
 
   bool HasDeviceAuthenticated(cryptauth::RemoteDeviceRef remote_device) {
@@ -546,8 +547,8 @@
   // operation was only constructed with |test_devices_[0]|, this operation
   // should not be affected.
   fake_ble_connection_manager_->RegisterRemoteDevice(
-      test_devices_[1].GetDeviceId(),
-      ConnectionReason::CONNECT_TETHERING_REQUEST);
+      test_devices_[1].GetDeviceId(), base::UnguessableToken::Create(),
+      secure_channel::ConnectionPriority::kLow);
   TransitionDeviceStatusFromDisconnectedToAuthenticated(test_devices_[1]);
   EXPECT_TRUE(fake_ble_connection_manager_->IsRegistered(
       test_devices_[0].GetDeviceId()));
@@ -572,8 +573,8 @@
   // Simulate the authentication of |test_devices_[0]|'s channel before
   // initialization.
   fake_ble_connection_manager_->RegisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::CONNECT_TETHERING_REQUEST);
+      test_devices_[0].GetDeviceId(), base::UnguessableToken::Create(),
+      secure_channel::ConnectionPriority::kLow);
   TransitionDeviceStatusFromDisconnectedToAuthenticated(test_devices_[0]);
 
   // Now initialize; the authentication handler should have been invoked.
@@ -604,8 +605,8 @@
   // Simulate the authentication of |test_devices_[0]|'s channel before
   // initialization.
   fake_ble_connection_manager_->RegisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::CONNECT_TETHERING_REQUEST);
+      test_devices_[0].GetDeviceId(), base::UnguessableToken::Create(),
+      secure_channel::ConnectionPriority::kLow);
   TransitionDeviceStatusFromDisconnectedToAuthenticated(test_devices_[0]);
 
   // Now initialize; the authentication handler should have been invoked.
@@ -638,8 +639,8 @@
 
   // Authenticate |test_devices_[0]|'s channel.
   fake_ble_connection_manager_->RegisterRemoteDevice(
-      test_devices_[0].GetDeviceId(),
-      ConnectionReason::CONNECT_TETHERING_REQUEST);
+      test_devices_[0].GetDeviceId(), base::UnguessableToken::Create(),
+      secure_channel::ConnectionPriority::kLow);
   TransitionDeviceStatusFromDisconnectedToAuthenticated(test_devices_[0]);
   EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[0]));
   EXPECT_TRUE(fake_ble_connection_manager_->IsRegistered(
@@ -659,8 +660,8 @@
 
   // Authenticate |test_devices_[2]|'s channel.
   fake_ble_connection_manager_->RegisterRemoteDevice(
-      test_devices_[2].GetDeviceId(),
-      ConnectionReason::CONNECT_TETHERING_REQUEST);
+      test_devices_[2].GetDeviceId(), base::UnguessableToken::Create(),
+      secure_channel::ConnectionPriority::kLow);
   TransitionDeviceStatusFromDisconnectedToAuthenticated(test_devices_[2]);
   EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[2]));
   EXPECT_TRUE(fake_ble_connection_manager_->IsRegistered(
diff --git a/chromeos/components/tether/tether_component_impl.cc b/chromeos/components/tether/tether_component_impl.cc
index fe964f6f..953e02f 100644
--- a/chromeos/components/tether/tether_component_impl.cc
+++ b/chromeos/components/tether/tether_component_impl.cc
@@ -19,6 +19,7 @@
 #include "chromeos/components/tether/tether_host_response_recorder.h"
 #include "chromeos/components/tether/tether_session_completion_logger.h"
 #include "chromeos/components/tether/wifi_hotspot_disconnector_impl.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 
 namespace chromeos {
@@ -69,6 +70,7 @@
 // static
 std::unique_ptr<TetherComponent> TetherComponentImpl::Factory::NewInstance(
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     TetherHostFetcher* tether_host_fetcher,
     NotificationPresenter* notification_presenter,
     GmsCoreNotificationsStateTrackerImpl* gms_core_notifications_state_tracker,
@@ -83,8 +85,9 @@
     factory_instance_ = new Factory();
 
   return factory_instance_->BuildInstance(
-      cryptauth_service, tether_host_fetcher, notification_presenter,
-      gms_core_notifications_state_tracker, pref_service, network_state_handler,
+      cryptauth_service, device_sync_client, tether_host_fetcher,
+      notification_presenter, gms_core_notifications_state_tracker,
+      pref_service, network_state_handler,
       managed_network_configuration_handler, network_connect,
       network_connection_handler, adapter, session_manager);
 }
@@ -105,6 +108,7 @@
 
 std::unique_ptr<TetherComponent> TetherComponentImpl::Factory::BuildInstance(
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     TetherHostFetcher* tether_host_fetcher,
     NotificationPresenter* notification_presenter,
     GmsCoreNotificationsStateTrackerImpl* gms_core_notifications_state_tracker,
@@ -116,14 +120,16 @@
     scoped_refptr<device::BluetoothAdapter> adapter,
     session_manager::SessionManager* session_manager) {
   return base::WrapUnique(new TetherComponentImpl(
-      cryptauth_service, tether_host_fetcher, notification_presenter,
-      gms_core_notifications_state_tracker, pref_service, network_state_handler,
+      cryptauth_service, device_sync_client, tether_host_fetcher,
+      notification_presenter, gms_core_notifications_state_tracker,
+      pref_service, network_state_handler,
       managed_network_configuration_handler, network_connect,
       network_connection_handler, adapter, session_manager));
 }
 
 TetherComponentImpl::TetherComponentImpl(
     cryptauth::CryptAuthService* cryptauth_service,
+    chromeos::device_sync::DeviceSyncClient* device_sync_client,
     TetherHostFetcher* tether_host_fetcher,
     NotificationPresenter* notification_presenter,
     GmsCoreNotificationsStateTrackerImpl* gms_core_notifications_state_tracker,
@@ -138,6 +144,7 @@
           AsynchronousShutdownObjectContainerImpl::Factory::NewInstance(
               adapter,
               cryptauth_service,
+              device_sync_client,
               tether_host_fetcher,
               network_state_handler,
               managed_network_configuration_handler,
diff --git a/chromeos/components/tether/tether_component_impl.h b/chromeos/components/tether/tether_component_impl.h
index 9df3458f9..d563a1eb 100644
--- a/chromeos/components/tether/tether_component_impl.h
+++ b/chromeos/components/tether/tether_component_impl.h
@@ -35,6 +35,10 @@
 class NetworkConnectionHandler;
 class NetworkStateHandler;
 
+namespace device_sync {
+class DeviceSyncClient;
+}  // namespace device_sync
+
 namespace tether {
 
 class AsynchronousShutdownObjectContainer;
@@ -53,6 +57,7 @@
    public:
     static std::unique_ptr<TetherComponent> NewInstance(
         cryptauth::CryptAuthService* cryptauth_service,
+        chromeos::device_sync::DeviceSyncClient* device_sync_client,
         TetherHostFetcher* tether_host_fetcher,
         NotificationPresenter* notification_presenter,
         GmsCoreNotificationsStateTrackerImpl*
@@ -71,6 +76,7 @@
    protected:
     virtual std::unique_ptr<TetherComponent> BuildInstance(
         cryptauth::CryptAuthService* cryptauth_service,
+        chromeos::device_sync::DeviceSyncClient* device_sync_client,
         TetherHostFetcher* tether_host_fetcher,
         NotificationPresenter* notification_presenter,
         GmsCoreNotificationsStateTrackerImpl*
@@ -96,6 +102,7 @@
  protected:
   TetherComponentImpl(
       cryptauth::CryptAuthService* cryptauth_service,
+      chromeos::device_sync::DeviceSyncClient* device_sync_client,
       TetherHostFetcher* tether_host_fetcher,
       NotificationPresenter* notification_presenter,
       GmsCoreNotificationsStateTrackerImpl*
diff --git a/chromeos/components/tether/tether_component_impl_unittest.cc b/chromeos/components/tether/tether_component_impl_unittest.cc
index d5762e5..2cf00a8c 100644
--- a/chromeos/components/tether/tether_component_impl_unittest.cc
+++ b/chromeos/components/tether/tether_component_impl_unittest.cc
@@ -52,6 +52,7 @@
   std::unique_ptr<AsynchronousShutdownObjectContainer> BuildInstance(
       scoped_refptr<device::BluetoothAdapter> adapter,
       cryptauth::CryptAuthService* cryptauth_service,
+      chromeos::device_sync::DeviceSyncClient* device_sync_client,
       TetherHostFetcher* tether_host_fetcher,
       NetworkStateHandler* network_state_handler,
       ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
@@ -157,8 +158,8 @@
         fake_crash_recovery_manager_factory_.get());
 
     component_ = TetherComponentImpl::Factory::NewInstance(
-        nullptr /* cryptauth_service */, nullptr /* tether_host_fetcher */,
-        nullptr /* notification_presenter */,
+        nullptr /* cryptauth_service */, nullptr /* device_sync_client */,
+        nullptr /* tether_host_fetcher */, nullptr /* notification_presenter */,
         nullptr /* gms_core_notifications_state_tracker */,
         nullptr /* pref_service */, nullptr /* network_state_handler */,
         nullptr /* managed_network_configuration_handler */,
diff --git a/chromeos/components/tether/tether_host_fetcher_impl.cc b/chromeos/components/tether/tether_host_fetcher_impl.cc
index 56471a1..7ac1910 100644
--- a/chromeos/components/tether/tether_host_fetcher_impl.cc
+++ b/chromeos/components/tether/tether_host_fetcher_impl.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/ptr_util.h"
+#include "chromeos/chromeos_features.h"
 #include "components/cryptauth/remote_device.h"
 #include "components/cryptauth/remote_device_provider.h"
 
@@ -20,11 +21,13 @@
 
 // static
 std::unique_ptr<TetherHostFetcher> TetherHostFetcherImpl::Factory::NewInstance(
-    cryptauth::RemoteDeviceProvider* remote_device_provider) {
+    cryptauth::RemoteDeviceProvider* remote_device_provider,
+    device_sync::DeviceSyncClient* device_sync_client) {
   if (!factory_instance_) {
     factory_instance_ = new Factory();
   }
-  return factory_instance_->BuildInstance(remote_device_provider);
+  return factory_instance_->BuildInstance(remote_device_provider,
+                                          device_sync_client);
 }
 
 // static
@@ -34,19 +37,30 @@
 
 std::unique_ptr<TetherHostFetcher>
 TetherHostFetcherImpl::Factory::BuildInstance(
-    cryptauth::RemoteDeviceProvider* remote_device_provider) {
-  return base::WrapUnique(new TetherHostFetcherImpl(remote_device_provider));
+    cryptauth::RemoteDeviceProvider* remote_device_provider,
+    device_sync::DeviceSyncClient* device_sync_client) {
+  return base::WrapUnique(
+      new TetherHostFetcherImpl(remote_device_provider, device_sync_client));
 }
 
 TetherHostFetcherImpl::TetherHostFetcherImpl(
-    cryptauth::RemoteDeviceProvider* remote_device_provider)
-    : remote_device_provider_(remote_device_provider) {
-  remote_device_provider_->AddObserver(this);
+    cryptauth::RemoteDeviceProvider* remote_device_provider,
+    device_sync::DeviceSyncClient* device_sync_client)
+    : remote_device_provider_(remote_device_provider),
+      device_sync_client_(device_sync_client) {
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi))
+    device_sync_client_->AddObserver(this);
+  else
+    remote_device_provider_->AddObserver(this);
+
   CacheCurrentTetherHosts();
 }
 
 TetherHostFetcherImpl::~TetherHostFetcherImpl() {
-  remote_device_provider_->RemoveObserver(this);
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi))
+    device_sync_client_->RemoveObserver(this);
+  else
+    remote_device_provider_->RemoveObserver(this);
 }
 
 bool TetherHostFetcherImpl::HasSyncedTetherHosts() {
@@ -69,13 +83,25 @@
   CacheCurrentTetherHosts();
 }
 
+void TetherHostFetcherImpl::OnNewDevicesSynced() {
+  CacheCurrentTetherHosts();
+}
+
 void TetherHostFetcherImpl::CacheCurrentTetherHosts() {
   cryptauth::RemoteDeviceRefList updated_list;
-  for (const auto& remote_device :
-       remote_device_provider_->GetSyncedDevices()) {
-    if (remote_device.supports_mobile_hotspot) {
-      updated_list.push_back(cryptauth::RemoteDeviceRef(
-          std::make_shared<cryptauth::RemoteDevice>(remote_device)));
+
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
+    for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
+      if (remote_device.supports_mobile_hotspot())
+        updated_list.push_back(remote_device);
+    }
+  } else {
+    for (const auto& remote_device :
+         remote_device_provider_->GetSyncedDevices()) {
+      if (remote_device.supports_mobile_hotspot) {
+        updated_list.push_back(cryptauth::RemoteDeviceRef(
+            std::make_shared<cryptauth::RemoteDevice>(remote_device)));
+      }
     }
   }
 
diff --git a/chromeos/components/tether/tether_host_fetcher_impl.h b/chromeos/components/tether/tether_host_fetcher_impl.h
index c532d13..f224a82 100644
--- a/chromeos/components/tether/tether_host_fetcher_impl.h
+++ b/chromeos/components/tether/tether_host_fetcher_impl.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "chromeos/components/tether/tether_host_fetcher.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "components/cryptauth/remote_device_provider.h"
 #include "components/cryptauth/remote_device_ref.h"
 
@@ -22,19 +23,30 @@
 
 // Concrete TetherHostFetcher implementation. Despite the asynchronous function
 // prototypes, callbacks are invoked synchronously.
+//
+// Note: TetherHostFetcherImpl, and the Tether feature as a whole, is currently
+// in the middle of a migration from using RemoteDeviceProvider to
+// DeviceSyncClient. Its constructor accepts both objects, but expects only of
+// them to be valid, and the other null (this is controlled at a higher level by
+// features::kMultiDeviceApi). Once Tether has fully migrated to DeviceSync Mojo
+// Service, RemoteDeviceProvider will be ripped out of this class. See
+// https://crbug.com/848956.
 class TetherHostFetcherImpl : public TetherHostFetcher,
-                              public cryptauth::RemoteDeviceProvider::Observer {
+                              public cryptauth::RemoteDeviceProvider::Observer,
+                              public device_sync::DeviceSyncClient::Observer {
  public:
   class Factory {
    public:
     static std::unique_ptr<TetherHostFetcher> NewInstance(
-        cryptauth::RemoteDeviceProvider* remote_device_provider);
+        cryptauth::RemoteDeviceProvider* remote_device_provider,
+        device_sync::DeviceSyncClient* device_sync_client);
 
     static void SetInstanceForTesting(Factory* factory);
 
    protected:
     virtual std::unique_ptr<TetherHostFetcher> BuildInstance(
-        cryptauth::RemoteDeviceProvider* remote_device_provider);
+        cryptauth::RemoteDeviceProvider* remote_device_provider,
+        device_sync::DeviceSyncClient* device_sync_client);
 
    private:
     static Factory* factory_instance_;
@@ -51,14 +63,20 @@
   // cryptauth::RemoteDeviceProvider::Observer:
   void OnSyncDeviceListChanged() override;
 
+  // device_sync::DeviceSyncClient::Observer:
+  void OnNewDevicesSynced() override;
+
  protected:
-  explicit TetherHostFetcherImpl(
-      cryptauth::RemoteDeviceProvider* remote_device_provider);
+  // TODO(crbug.com/848956): Remove RemoteDeviceProvider once all clients have
+  // migrated to the DeviceSync Mojo API.
+  TetherHostFetcherImpl(cryptauth::RemoteDeviceProvider* remote_device_provider,
+                        device_sync::DeviceSyncClient* device_sync_client);
 
  private:
   void CacheCurrentTetherHosts();
 
   cryptauth::RemoteDeviceProvider* remote_device_provider_;
+  device_sync::DeviceSyncClient* device_sync_client_;
 
   cryptauth::RemoteDeviceRefList current_remote_device_list_;
 
diff --git a/chromeos/components/tether/tether_host_fetcher_impl_unittest.cc b/chromeos/components/tether/tether_host_fetcher_impl_unittest.cc
index 3bc8744..ad2ca6b3 100644
--- a/chromeos/components/tether/tether_host_fetcher_impl_unittest.cc
+++ b/chromeos/components/tether/tether_host_fetcher_impl_unittest.cc
@@ -10,6 +10,9 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/optional.h"
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/chromeos_features.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "components/cryptauth/fake_remote_device_provider.h"
 #include "components/cryptauth/remote_device.h"
 #include "components/cryptauth/remote_device_ref.h"
@@ -50,12 +53,31 @@
   void SetUp() override {
     fake_remote_device_provider_ =
         std::make_unique<cryptauth::FakeRemoteDeviceProvider>();
-    fake_remote_device_provider_->set_synced_remote_devices(
-        test_remote_device_list_);
+    fake_device_sync_client_ =
+        std::make_unique<device_sync::FakeDeviceSyncClient>();
+  }
+
+  void TearDown() override {
+    // |tether_host_fetcher_| needs to be deleted before |scoped_feature_list_|
+    // is deleted. Without this, |tether_host_fetcher_|'s destructor will run
+    // without the changes that were made to |scoped_feature_list_|.
+    tether_host_fetcher_.reset();
+  }
+
+  void SetMultiDeviceApiEnabled() {
+    scoped_feature_list_.InitAndEnableFeature(features::kMultiDeviceApi);
+  }
+
+  void InitializeTest() {
+    SetSyncedDevices(test_remote_device_list_);
 
     tether_host_fetcher_ = TetherHostFetcherImpl::Factory::NewInstance(
-        fake_remote_device_provider_.get());
-
+        base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
+            ? nullptr
+            : fake_remote_device_provider_.get(),
+        base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
+            ? fake_device_sync_client_.get()
+            : nullptr);
     test_observer_ = std::make_unique<TestObserver>();
     tether_host_fetcher_->AddObserver(test_observer_.get());
   }
@@ -94,7 +116,7 @@
   cryptauth::RemoteDeviceList CreateTestRemoteDeviceList() {
     cryptauth::RemoteDeviceList list =
         cryptauth::CreateRemoteDeviceListForTest(kNumTestDevices);
-    for (auto device : list)
+    for (auto& device : list)
       device.supports_mobile_hotspot = true;
 
     return list;
@@ -110,6 +132,102 @@
     return list;
   }
 
+  void SetSyncedDevices(cryptauth::RemoteDeviceList devices) {
+    if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
+      fake_device_sync_client_->set_synced_devices(
+          CreateTestRemoteDeviceRefList(devices));
+    } else {
+      fake_remote_device_provider_->set_synced_remote_devices(devices);
+    }
+  }
+
+  void NotifyNewDevicesSynced() {
+    if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi))
+      fake_device_sync_client_->NotifyNewDevicesSynced();
+    else
+      fake_remote_device_provider_->NotifyObserversDeviceListChanged();
+  }
+
+  void TestHasSyncedTetherHosts() {
+    InitializeTest();
+
+    EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
+    EXPECT_EQ(0u, test_observer_->num_updates());
+
+    // Update the list of devices to be empty.
+    SetSyncedDevices(cryptauth::RemoteDeviceList());
+    NotifyNewDevicesSynced();
+    EXPECT_FALSE(tether_host_fetcher_->HasSyncedTetherHosts());
+    EXPECT_EQ(1u, test_observer_->num_updates());
+
+    // Notify that the list has changed, even though it hasn't. There should be
+    // no update.
+    NotifyNewDevicesSynced();
+    EXPECT_FALSE(tether_host_fetcher_->HasSyncedTetherHosts());
+    EXPECT_EQ(1u, test_observer_->num_updates());
+
+    // Update the list to include device 0 only.
+    SetSyncedDevices({test_remote_device_list_[0]});
+    NotifyNewDevicesSynced();
+    EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
+    EXPECT_EQ(2u, test_observer_->num_updates());
+
+    // Notify that the list has changed, even though it hasn't. There should be
+    // no update.
+    NotifyNewDevicesSynced();
+    EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
+    EXPECT_EQ(2u, test_observer_->num_updates());
+  }
+
+  void TestSingleTetherHost() {
+    InitializeTest();
+
+    VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
+                           test_remote_device_ref_list_[0]);
+
+    // Now, set device 0 as the only device. It should still be returned when
+    // requested.
+    SetSyncedDevices(cryptauth::RemoteDeviceList{test_remote_device_list_[0]});
+    NotifyNewDevicesSynced();
+    VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
+                           test_remote_device_ref_list_[0]);
+
+    // Now, set another device as the only device, but remove its mobile data
+    // support. It should not be returned.
+    cryptauth::RemoteDevice remote_device = cryptauth::RemoteDevice();
+    remote_device.supports_mobile_hotspot = false;
+
+    SetSyncedDevices(cryptauth::RemoteDeviceList{remote_device});
+    NotifyNewDevicesSynced();
+    VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
+                           base::nullopt);
+
+    // Update the list; now, there are no more devices.
+    SetSyncedDevices(cryptauth::RemoteDeviceList());
+    NotifyNewDevicesSynced();
+    VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
+                           base::nullopt);
+  }
+
+  void TestFetchAllTetherHosts() {
+    InitializeTest();
+
+    // Create a list of test devices, only some of which are valid tether hosts.
+    // Ensure that only that subset is fetched.
+
+    test_remote_device_list_[3].supports_mobile_hotspot = false;
+    test_remote_device_list_[4].supports_mobile_hotspot = false;
+
+    cryptauth::RemoteDeviceRefList host_device_list(
+        CreateTestRemoteDeviceRefList({test_remote_device_list_[0],
+                                       test_remote_device_list_[1],
+                                       test_remote_device_list_[2]}));
+
+    SetSyncedDevices(test_remote_device_list_);
+    NotifyNewDevicesSynced();
+    VerifyAllTetherHosts(host_device_list);
+  }
+
   cryptauth::RemoteDeviceList test_remote_device_list_;
   cryptauth::RemoteDeviceRefList test_remote_device_ref_list_;
 
@@ -119,94 +237,55 @@
 
   std::unique_ptr<cryptauth::FakeRemoteDeviceProvider>
       fake_remote_device_provider_;
+  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
 
   std::unique_ptr<TetherHostFetcher> tether_host_fetcher_;
 
+  base::test::ScopedFeatureList scoped_feature_list_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(TetherHostFetcherImplTest);
 };
 
 TEST_F(TetherHostFetcherImplTest, TestHasSyncedTetherHosts) {
-  EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
-  EXPECT_EQ(0u, test_observer_->num_updates());
+  TestHasSyncedTetherHosts();
+}
 
-  // Update the list of devices to be empty.
-  fake_remote_device_provider_->set_synced_remote_devices(
-      cryptauth::RemoteDeviceList());
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  EXPECT_FALSE(tether_host_fetcher_->HasSyncedTetherHosts());
-  EXPECT_EQ(1u, test_observer_->num_updates());
-
-  // Notify that the list has changed, even though it hasn't. There should be no
-  // update.
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  EXPECT_FALSE(tether_host_fetcher_->HasSyncedTetherHosts());
-  EXPECT_EQ(1u, test_observer_->num_updates());
-
-  // Update the list to include device 0 only.
-  fake_remote_device_provider_->set_synced_remote_devices(
-      {test_remote_device_list_[0]});
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
-  EXPECT_EQ(2u, test_observer_->num_updates());
-
-  // Notify that the list has changed, even though it hasn't. There should be no
-  // update.
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  EXPECT_TRUE(tether_host_fetcher_->HasSyncedTetherHosts());
-  EXPECT_EQ(2u, test_observer_->num_updates());
+TEST_F(TetherHostFetcherImplTest,
+       TestHasSyncedTetherHosts_MultideviceApiEnabled) {
+  SetMultiDeviceApiEnabled();
+  TestHasSyncedTetherHosts();
 }
 
 TEST_F(TetherHostFetcherImplTest, TestFetchAllTetherHosts) {
-  // Create a list of test devices, only some of which are valid tether hosts.
-  // Ensure that only that subset is fetched.
+  TestFetchAllTetherHosts();
+}
 
-  test_remote_device_list_[3].supports_mobile_hotspot = false;
-  test_remote_device_list_[4].supports_mobile_hotspot = false;
-
-  cryptauth::RemoteDeviceRefList host_device_list(CreateTestRemoteDeviceRefList(
-      {test_remote_device_list_[0], test_remote_device_list_[1],
-       test_remote_device_list_[2]}));
-
-  fake_remote_device_provider_->set_synced_remote_devices(
-      test_remote_device_list_);
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  VerifyAllTetherHosts(host_device_list);
+TEST_F(TetherHostFetcherImplTest,
+       TestFetchAllTetherHosts_MultideviceApiEnabled) {
+  SetMultiDeviceApiEnabled();
+  TestFetchAllTetherHosts();
 }
 
 TEST_F(TetherHostFetcherImplTest, TestSingleTetherHost) {
-  VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
-                         test_remote_device_ref_list_[0]);
+  TestSingleTetherHost();
+}
 
-  // Now, set device 0 as the only device. It should still be returned when
-  // requested.
-  fake_remote_device_provider_->set_synced_remote_devices(
-      cryptauth::RemoteDeviceList{test_remote_device_list_[0]});
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
-                         test_remote_device_ref_list_[0]);
-
-  // Now, set another device as the only device, but remove its mobile data
-  // support. It should not be returned.
-  cryptauth::RemoteDevice remote_device = cryptauth::RemoteDevice();
-  remote_device.supports_mobile_hotspot = false;
-
-  fake_remote_device_provider_->set_synced_remote_devices(
-      cryptauth::RemoteDeviceList{remote_device});
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
-                         base::nullopt);
-
-  // Update the list; now, there are no more devices.
-  fake_remote_device_provider_->set_synced_remote_devices(
-      cryptauth::RemoteDeviceList());
-  fake_remote_device_provider_->NotifyObserversDeviceListChanged();
-  VerifySingleTetherHost(test_remote_device_ref_list_[0].GetDeviceId(),
-                         base::nullopt);
+TEST_F(TetherHostFetcherImplTest, TestSingleTetherHost_MultideviceApiEnabled) {
+  SetMultiDeviceApiEnabled();
+  TestSingleTetherHost();
 }
 
 TEST_F(TetherHostFetcherImplTest,
        TestSingleTetherHost_IdDoesNotCorrespondToDevice) {
+  InitializeTest();
+  VerifySingleTetherHost("nonexistentId", base::nullopt);
+}
+
+TEST_F(TetherHostFetcherImplTest,
+       TestSingleTetherHost_IdDoesNotCorrespondToDevice_MultideviceApiEnabled) {
+  SetMultiDeviceApiEnabled();
+  InitializeTest();
   VerifySingleTetherHost("nonexistentId", base::nullopt);
 }
 
diff --git a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
index a945a05..faa7c9be 100644
--- a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
+++ b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
@@ -8,6 +8,7 @@
 
 #include "chromeos/services/device_sync/public/cpp/device_sync_client_impl.h"
 
+#include "base/no_destructor.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/services/device_sync/public/mojom/constants.mojom.h"
 #include "chromeos/services/device_sync/public/mojom/device_sync.mojom.h"
@@ -19,6 +20,32 @@
 
 namespace device_sync {
 
+// static
+DeviceSyncClientImpl::Factory* DeviceSyncClientImpl::Factory::test_factory_ =
+    nullptr;
+
+// static
+DeviceSyncClientImpl::Factory* DeviceSyncClientImpl::Factory::Get() {
+  if (test_factory_)
+    return test_factory_;
+
+  static base::NoDestructor<Factory> factory;
+  return factory.get();
+}
+
+// static
+void DeviceSyncClientImpl::Factory::SetInstanceForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+DeviceSyncClientImpl::Factory::~Factory() = default;
+
+std::unique_ptr<DeviceSyncClient> DeviceSyncClientImpl::Factory::BuildInstance(
+    service_manager::Connector* connector) {
+  return base::WrapUnique(new DeviceSyncClientImpl(connector));
+}
+
 DeviceSyncClientImpl::DeviceSyncClientImpl(
     service_manager::Connector* connector)
     : DeviceSyncClientImpl(connector, base::ThreadTaskRunnerHandle::Get()) {}
diff --git a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.h b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.h
index ced7e4a..381c3798 100644
--- a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.h
+++ b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.h
@@ -39,6 +39,18 @@
 class DeviceSyncClientImpl : public DeviceSyncClient,
                              public device_sync::mojom::DeviceSyncObserver {
  public:
+  class Factory {
+   public:
+    static Factory* Get();
+    static void SetInstanceForTesting(Factory* test_factory);
+    virtual ~Factory();
+    virtual std::unique_ptr<DeviceSyncClient> BuildInstance(
+        service_manager::Connector* connector);
+
+   private:
+    static Factory* test_factory_;
+  };
+
   explicit DeviceSyncClientImpl(service_manager::Connector* connector);
   ~DeviceSyncClientImpl() override;
 
diff --git a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
index 3aee9ffc..69b4445a 100644
--- a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
+++ b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
@@ -27,6 +27,9 @@
   FakeDeviceSyncClient();
   ~FakeDeviceSyncClient() override;
 
+  using DeviceSyncClient::NotifyEnrollmentFinished;
+  using DeviceSyncClient::NotifyNewDevicesSynced;
+
   void InvokePendingSetSoftwareFeatureStateCallback(
       const base::Optional<std::string>& error_code);
   void InvokePendingFindEligibleDevicesCallback(
diff --git a/chromeos/services/secure_channel/BUILD.gn b/chromeos/services/secure_channel/BUILD.gn
index 4d28be1..2e276fc 100644
--- a/chromeos/services/secure_channel/BUILD.gn
+++ b/chromeos/services/secure_channel/BUILD.gn
@@ -60,6 +60,7 @@
     "//base",
     "//chromeos/components/proximity_auth/logging",
     "//chromeos/services/secure_channel/public/cpp/shared",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//chromeos/services/secure_channel/public/mojom",
     "//components/cryptauth",
   ]
@@ -103,6 +104,7 @@
     ":secure_channel",
     "//base",
     "//chromeos/services/secure_channel/public/cpp/shared",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//chromeos/services/secure_channel/public/mojom",
     "//components/cryptauth:cryptauth",
   ]
@@ -132,6 +134,7 @@
     ":test_support",
     "//base/test:test_support",
     "//chromeos/services/secure_channel/public/cpp/shared",
+    "//chromeos/services/secure_channel/public/cpp/shared:connection_priority",
     "//chromeos/services/secure_channel/public/cpp/shared:test_support",
     "//chromeos/services/secure_channel/public/mojom",
     "//components/cryptauth:test_support",
diff --git a/chromeos/services/secure_channel/connection_details.cc b/chromeos/services/secure_channel/connection_details.cc
index 9d1a36c..da1cea4 100644
--- a/chromeos/services/secure_channel/connection_details.cc
+++ b/chromeos/services/secure_channel/connection_details.cc
@@ -24,6 +24,10 @@
          connection_medium() == other.connection_medium();
 }
 
+bool ConnectionDetails::operator!=(const ConnectionDetails& other) const {
+  return !(*this == other);
+}
+
 bool ConnectionDetails::operator<(const ConnectionDetails& other) const {
   if (device_id() != other.device_id())
     return device_id() < other.device_id();
diff --git a/chromeos/services/secure_channel/connection_details.h b/chromeos/services/secure_channel/connection_details.h
index 994ebe7..feed095 100644
--- a/chromeos/services/secure_channel/connection_details.h
+++ b/chromeos/services/secure_channel/connection_details.h
@@ -29,6 +29,7 @@
   ConnectionMedium connection_medium() const { return connection_medium_; }
 
   bool operator==(const ConnectionDetails& other) const;
+  bool operator!=(const ConnectionDetails& other) const;
   bool operator<(const ConnectionDetails& other) const;
 
  private:
diff --git a/chromeos/services/secure_channel/fake_pending_connection_manager.cc b/chromeos/services/secure_channel/fake_pending_connection_manager.cc
index cc99a01..0c10d57c 100644
--- a/chromeos/services/secure_channel/fake_pending_connection_manager.cc
+++ b/chromeos/services/secure_channel/fake_pending_connection_manager.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/services/secure_channel/fake_pending_connection_manager.h"
 
+#include "base/logging.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/authenticated_channel.h"
 
 namespace chromeos {
@@ -15,13 +16,37 @@
 
 FakePendingConnectionManager::~FakePendingConnectionManager() = default;
 
+void FakePendingConnectionManager::NotifyConnectionForHandledRequests(
+    std::unique_ptr<AuthenticatedChannel> authenticated_channel,
+    const ConnectionDetails& connection_details) {
+  std::vector<std::unique_ptr<ClientConnectionParameters>> client_list;
+
+  auto it = handled_requests_.begin();
+  while (it != handled_requests_.end()) {
+    if (std::get<0>(*it) != connection_details) {
+      ++it;
+      continue;
+    }
+
+    client_list.push_back(std::move(std::get<1>(*it)));
+    it = handled_requests_.erase(it);
+  }
+
+  // There must be at least one client in the list.
+  DCHECK_LT(0u, client_list.size());
+
+  NotifyOnConnection(std::move(authenticated_channel), std::move(client_list),
+                     connection_details);
+}
+
 void FakePendingConnectionManager::HandleConnectionRequest(
     const ConnectionDetails& connection_details,
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
-    ConnectionRole connection_role) {
+    ConnectionRole connection_role,
+    ConnectionPriority connection_priority) {
   handled_requests_.push_back(std::make_tuple(
       connection_details, std::move(client_connection_parameters),
-      connection_role));
+      connection_role, connection_priority));
 }
 
 FakePendingConnectionManagerDelegate::FakePendingConnectionManagerDelegate() =
diff --git a/chromeos/services/secure_channel/fake_pending_connection_manager.h b/chromeos/services/secure_channel/fake_pending_connection_manager.h
index caa0d24..cfa0866 100644
--- a/chromeos/services/secure_channel/fake_pending_connection_manager.h
+++ b/chromeos/services/secure_channel/fake_pending_connection_manager.h
@@ -13,6 +13,7 @@
 #include "chromeos/services/secure_channel/connection_details.h"
 #include "chromeos/services/secure_channel/connection_role.h"
 #include "chromeos/services/secure_channel/pending_connection_manager.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -27,17 +28,24 @@
   using HandledRequestsList =
       std::vector<std::tuple<ConnectionDetails,
                              std::unique_ptr<ClientConnectionParameters>,
-                             ConnectionRole>>;
+                             ConnectionRole,
+                             ConnectionPriority>>;
   HandledRequestsList& handled_requests() { return handled_requests_; }
 
-  // Make NotifyOnConnection() public for testing.
-  using PendingConnectionManager::NotifyOnConnection;
+  // Notifies the delegate that the a connection was successful for the attempt
+  // associated with |connection_details|. Before this call can complete, there
+  // must be at least one handled request with those details. This call removes
+  // the relevant handled requests from the list returned by handled_requests().
+  void NotifyConnectionForHandledRequests(
+      std::unique_ptr<AuthenticatedChannel> authenticated_channel,
+      const ConnectionDetails& connection_details);
 
  private:
   void HandleConnectionRequest(
       const ConnectionDetails& connection_details,
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
-      ConnectionRole connection_role) override;
+      ConnectionRole connection_role,
+      ConnectionPriority connection_priority) override;
 
   HandledRequestsList handled_requests_;
 
diff --git a/chromeos/services/secure_channel/pending_connection_manager.h b/chromeos/services/secure_channel/pending_connection_manager.h
index 637d144..58f197b 100644
--- a/chromeos/services/secure_channel/pending_connection_manager.h
+++ b/chromeos/services/secure_channel/pending_connection_manager.h
@@ -12,6 +12,7 @@
 #include "chromeos/services/secure_channel/client_connection_parameters.h"
 #include "chromeos/services/secure_channel/connection_details.h"
 #include "chromeos/services/secure_channel/connection_role.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -42,7 +43,8 @@
   virtual void HandleConnectionRequest(
       const ConnectionDetails& connection_details,
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
-      ConnectionRole connection_role) = 0;
+      ConnectionRole connection_role,
+      ConnectionPriority connection_priority) = 0;
 
  protected:
   PendingConnectionManager(Delegate* delegate);
diff --git a/chromeos/services/secure_channel/pending_connection_manager_impl.cc b/chromeos/services/secure_channel/pending_connection_manager_impl.cc
index 0dfdfcb4..5476eb4b 100644
--- a/chromeos/services/secure_channel/pending_connection_manager_impl.cc
+++ b/chromeos/services/secure_channel/pending_connection_manager_impl.cc
@@ -46,7 +46,8 @@
 void PendingConnectionManagerImpl::HandleConnectionRequest(
     const ConnectionDetails& connection_details,
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
-    ConnectionRole connection_role) {
+    ConnectionRole connection_role,
+    ConnectionPriority connection_priority) {
   NOTIMPLEMENTED();
 }
 
diff --git a/chromeos/services/secure_channel/pending_connection_manager_impl.h b/chromeos/services/secure_channel/pending_connection_manager_impl.h
index f4bdcb0..0543671 100644
--- a/chromeos/services/secure_channel/pending_connection_manager_impl.h
+++ b/chromeos/services/secure_channel/pending_connection_manager_impl.h
@@ -13,6 +13,7 @@
 #include "chromeos/services/secure_channel/connection_details.h"
 #include "chromeos/services/secure_channel/connection_role.h"
 #include "chromeos/services/secure_channel/pending_connection_manager.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
 
@@ -42,7 +43,8 @@
   void HandleConnectionRequest(
       const ConnectionDetails& connection_details,
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
-      ConnectionRole connection_role) override;
+      ConnectionRole connection_role,
+      ConnectionPriority connection_priority) override;
 
   DISALLOW_COPY_AND_ASSIGN(PendingConnectionManagerImpl);
 };
diff --git a/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn b/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
index 4f020b7..41bd9f4 100644
--- a/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
+++ b/chromeos/services/secure_channel/public/cpp/shared/BUILD.gn
@@ -17,6 +17,20 @@
   ]
 }
 
+# Note: ConnectionPriority is in a separate target to avoid a circular
+# dependency between //chromeos/services/secure_channel/public/mojom and
+# //chromeos/services/secure_channel/public/cpp/shared.
+source_set("connection_priority") {
+  sources = [
+    "connection_priority.cc",
+    "connection_priority.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
+
 static_library("test_support") {
   testonly = true
 
diff --git a/chromeos/services/secure_channel/public/cpp/shared/connection_priority.cc b/chromeos/services/secure_channel/public/cpp/shared/connection_priority.cc
new file mode 100644
index 0000000..5c5346f
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/shared/connection_priority.cc
@@ -0,0 +1,29 @@
+// 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.
+
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+
+namespace chromeos {
+
+namespace secure_channel {
+
+std::ostream& operator<<(std::ostream& stream,
+                         const ConnectionPriority& connection_priority) {
+  switch (connection_priority) {
+    case ConnectionPriority::kLow:
+      stream << "[low priority]";
+      break;
+    case ConnectionPriority::kMedium:
+      stream << "[medium priority]";
+      break;
+    case ConnectionPriority::kHigh:
+      stream << "[high priority]";
+      break;
+  }
+  return stream;
+}
+
+}  // namespace secure_channel
+
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/public/cpp/shared/connection_priority.h b/chromeos/services/secure_channel/public/cpp/shared/connection_priority.h
new file mode 100644
index 0000000..e735701
--- /dev/null
+++ b/chromeos/services/secure_channel/public/cpp/shared/connection_priority.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_CONNECTION_PRIORITY_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_CONNECTION_PRIORITY_H_
+
+#include <ostream>
+
+namespace chromeos {
+
+namespace secure_channel {
+
+// Determines the order in which connections are attempted when system resources
+// must be shared. For example, a device can only register a limited number of
+// BLE advertisements at a given time due to hardware constraints; in this
+// situation, a connection attempt with a higher priority will be allowed to
+// register an advertisement before an attempt with a lower priority.
+//
+// For connection mediums which do not require use of limited system resources,
+// ConnectionPriority is ignored.
+enum class ConnectionPriority {
+  // Should be used for connection attempts which do not have latency
+  // requirements (e.g., background scans for nearby devices).
+  kLow = 1,
+
+  // Should be used when the connection attempt should complete in a reasonable
+  // amount of time but is not urgent (e.g., heartbeat/keep-alive messages).
+  kMedium = 2,
+
+  // Should be used when the user is directly waiting on the result of the
+  // connection (e.g., the user clicks a button and sees a spinner in the UI
+  // until the connection succeeds).
+  kHigh = 3
+};
+
+std::ostream& operator<<(std::ostream& stream,
+                         const ConnectionPriority& connection_priority);
+
+}  // namespace secure_channel
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PUBLIC_CPP_SHARED_CONNECTION_PRIORITY_H_
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 9174f7c..f5505e6 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -118,10 +118,26 @@
   defines = [ "ARC_IMPLEMENTATION" ]
 
   deps = [
+    ":arc_base_enums",
     "//components/prefs",
   ]
 }
 
+static_library("arc_base_enums") {
+  sources = [
+    "arc_instance_mode.cc",
+    "arc_instance_mode.h",
+    "arc_stop_reason.cc",
+    "arc_stop_reason.h",
+    "arc_supervision_transition.cc",
+    "arc_supervision_transition.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
+
 static_library("arc_base") {
   # TODO(hidehiko): Revisit here and move back some files to "arc"
   # on completion to move ArcSession task to ArcSessionManager.
@@ -135,8 +151,6 @@
     "arc_data_remover.h",
     "arc_features.cc",
     "arc_features.h",
-    "arc_instance_mode.cc",
-    "arc_instance_mode.h",
     "arc_service_manager.cc",
     "arc_service_manager.h",
     "arc_session.cc",
@@ -145,8 +159,6 @@
     "arc_session_impl.h",
     "arc_session_runner.cc",
     "arc_session_runner.h",
-    "arc_stop_reason.cc",
-    "arc_stop_reason.h",
     "arc_util.cc",
     "arc_util.h",
   ]
@@ -167,6 +179,7 @@
   ]
 
   public_deps = [
+    ":arc_base_enums",
     ":connection_holder",
     ":prefs",
     "//components/arc/common",
diff --git a/components/arc/arc_prefs.cc b/components/arc/arc_prefs.cc
index d60f5d5..82c450b 100644
--- a/components/arc/arc_prefs.cc
+++ b/components/arc/arc_prefs.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/arc/arc_prefs.h"
+#include "components/arc/arc_supervision_transition.h"
 
 #include <string>
 
@@ -40,6 +41,9 @@
 // A preference that indicated whether Android reported it's compliance status
 // with provided policies. This is used only as a signal to start Android kiosk.
 const char kArcPolicyComplianceReported[] = "arc.policy_compliance_reported";
+// A preference that indicates that a supervision transition is necessary, in
+// response to a CHILD_ACCOUNT transiting to a REGULAR_ACCOUNT or vice-versa.
+const char kArcSupervisionTransition[] = "arc.supervision_transition";
 // A preference that indicates that user accepted PlayStore terms.
 const char kArcTermsAccepted[] = "arc.terms.accepted";
 // A preference that indicates that ToS was shown in OOBE flow.
@@ -99,6 +103,10 @@
   // This is used to decide whether migration from ecryptfs to ext4 is allowed.
   registry->RegisterIntegerPref(prefs::kEcryptfsMigrationStrategy, 0);
 
+  registry->RegisterIntegerPref(
+      kArcSupervisionTransition,
+      static_cast<int>(ArcSupervisionTransition::NO_TRANSITION));
+
   // Sorted in lexicographical order.
   registry->RegisterBooleanPref(kArcDataRemoveRequested, false);
   registry->RegisterBooleanPref(kArcEnabled, false);
diff --git a/components/arc/arc_prefs.h b/components/arc/arc_prefs.h
index 99fd759c..24b996e 100644
--- a/components/arc/arc_prefs.h
+++ b/components/arc/arc_prefs.h
@@ -29,6 +29,7 @@
 ARC_EXPORT extern const char kArcPushInstallAppsPending[];
 ARC_EXPORT extern const char kArcSetNotificationsEnabledDeferred[];
 ARC_EXPORT extern const char kArcSignedIn[];
+ARC_EXPORT extern const char kArcSupervisionTransition[];
 ARC_EXPORT extern const char kArcCompatibleFilesystemChosen[];
 ARC_EXPORT extern const char kArcVoiceInteractionValuePropAccepted[];
 ARC_EXPORT extern const char kEcryptfsMigrationStrategy[];
diff --git a/components/arc/arc_supervision_transition.cc b/components/arc/arc_supervision_transition.cc
new file mode 100644
index 0000000..d9b03ff
--- /dev/null
+++ b/components/arc/arc_supervision_transition.cc
@@ -0,0 +1,27 @@
+// 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.
+
+#include "components/arc/arc_supervision_transition.h"
+#include "base/logging.h"
+
+namespace arc {
+
+std::ostream& operator<<(std::ostream& os,
+                         ArcSupervisionTransition supervision_transition) {
+  switch (supervision_transition) {
+    case ArcSupervisionTransition::NO_TRANSITION:
+      return os << "NO_TRANSITION";
+    case ArcSupervisionTransition::CHILD_TO_REGULAR:
+      return os << "CHILD_TO_REGULAR";
+    case ArcSupervisionTransition::REGULAR_TO_CHILD:
+      return os << "REGULAR_TO_CHILD";
+  }
+  NOTREACHED() << "Unexpected value for ArcSupervisionTransition: "
+               << static_cast<int>(supervision_transition);
+
+  return os << "ArcSupervisionTransition("
+            << static_cast<int>(supervision_transition) << ")";
+}
+
+}  // namespace arc
diff --git a/components/arc/arc_supervision_transition.h b/components/arc/arc_supervision_transition.h
new file mode 100644
index 0000000..bc3fb9f
--- /dev/null
+++ b/components/arc/arc_supervision_transition.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef COMPONENTS_ARC_ARC_SUPERVISION_TRANSITION_H_
+#define COMPONENTS_ARC_ARC_SUPERVISION_TRANSITION_H_
+
+#include <ostream>
+
+namespace arc {
+
+// These values must be kept in sync with
+// UpgradeArcInstanceRequest.SupervisionTransition in
+// third_party/cros_system_api/dbus/arc.proto.
+enum class ArcSupervisionTransition : int {
+  // No transition necessary.
+  NO_TRANSITION = 0,
+  // Child user is transitioning to a regular account, need to lift
+  // supervision.
+  CHILD_TO_REGULAR = 1,
+  // Regular user is transitioning to a child account, need to enable
+  // supervision.
+  REGULAR_TO_CHILD = 2,
+};
+
+std::ostream& operator<<(std::ostream& os,
+                         ArcSupervisionTransition supervisionTransition);
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_ARC_SUPERVISION_TRANSITION_H_
diff --git a/components/arc/common/auth.mojom b/components/arc/common/auth.mojom
index e197591..5cf12bd 100644
--- a/components/arc/common/auth.mojom
+++ b/components/arc/common/auth.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 13
+// Next MinVersion: 14
 
 module arc.mojom;
 
@@ -99,6 +99,32 @@
     // corresponding UMA callsite in Chrome arc::UpdateAuthAccountCheckStatus.
 };
 
+// These values describe the result of ARC attempting to change supervision
+// state after an account type change.
+[Extensible]
+enum SupervisionChangeStatus {
+  // CloudDPC supervision was disabled successfully.
+  [MinVersion=13] CLOUD_DPC_DISABLED = 0,
+
+  // CloudDPC supervision was already disabled.
+  [MinVersion=13] CLOUD_DPC_ALREADY_DISABLED = 1,
+
+  // CloudDPC supervision was enabled successfully.
+  [MinVersion=13] CLOUD_DPC_ENABLED = 2,
+
+  // CloudDPC supervision was already enabled.
+  [MinVersion=13] CLOUD_DPC_ALREADY_ENABLED = 3,
+
+  // Invalid state returned from Chrome.
+  [MinVersion=13] INVALID_SUPERVISION_STATE = 4,
+
+  // Failed to disable CloudDPC due to an unspecified error.
+  [MinVersion=13] CLOUD_DPC_DISABLING_FAILED = 5,
+
+  // Failed to enable CloudDPC due to an unspecified error.
+  [MinVersion=13] CLOUD_DPC_ENABLING_FAILED = 6,
+};
+
 // These values describe the type of the Chrome account to provision.
 [Extensible]
 enum ChromeAccountType {
@@ -144,8 +170,8 @@
   [MinVersion=9] string? account_name@4;
 
   // The authorization code that can be used in Android to sign in when
-  // account_type is USER_ACCOUNT or ROBOT_ACCOUNT. If it is null in these
-  // cases, sign-in will be skipped.
+  // account_type is USER_ACCOUNT, ROBOT_ACCOUNT or CHILD_ACCOUNT. If it is
+  // null in these cases, sign-in will be skipped.
   string? auth_code@0;
 
   // If account_type is ACTIVE_DIRECTORY_ACCOUNT, this contains an enrollment
@@ -160,7 +186,7 @@
   bool is_managed@2;
 };
 
-// Next Method ID: 11.
+// Next Method ID: 12.
 interface AuthHost {
   // Notifies Chrome that the authorization flow is completed and provides
   // resulting |status|. If |initial_signin| is true then this indicates that
@@ -185,6 +211,13 @@
 
   // Reports result of account check.
   [MinVersion=9] ReportAccountCheckStatus@9(AccountCheckStatus status);
+
+  // Reports to Chrome the result of changing the supervision state.
+  // Chrome informs ARC on every boot if a supervision transition is necessary
+  // or not (see https://crrev.com/c/1069031). ARC should report back only if
+  // a transition was necessary.
+  [MinVersion=13] ReportSupervisionChangeStatus@11(
+      SupervisionChangeStatus status);
 };
 
 // Next Method ID: 3
@@ -204,4 +237,5 @@
   // of failure, other than SUCCESS.
   [MinVersion=5] OnAccountInfoReady@1(
       AccountInfo? account_info, [MinVersion=10] ArcSignInStatus status);
+
 };
diff --git a/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.cc b/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.cc
index ae75344..f0d611a 100644
--- a/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.cc
+++ b/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.cc
@@ -269,18 +269,6 @@
     RecordBleWeaveConnectionResult(result);
   }
 
-  if (result ==
-          BleWeaveConnectionResult::
-              BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_FINDING_GATT_CHARACTERISTICS ||
-      result ==
-          BleWeaveConnectionResult::
-              BLE_WEAVE_CONNECTION_RESULT_ERROR_FINDING_GATT_CHARACTERISTICS ||
-      result ==
-          BleWeaveConnectionResult::
-              BLE_WEAVE_CONNECTION_RESULT_ERROR_GATT_CHARACTERISTIC_NOT_AVAILABLE) {
-    NotifyGattCharacteristicsNotAvailable();
-  }
-
   if (adapter_) {
     adapter_->RemoveObserver(this);
     adapter_ = nullptr;
diff --git a/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection_unittest.cc b/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection_unittest.cc
index a84264e..441fe6a 100644
--- a/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection_unittest.cc
+++ b/components/cryptauth/ble/bluetooth_low_energy_weave_client_connection_unittest.cc
@@ -268,7 +268,6 @@
   MockConnectionObserver(Connection* connection)
       : connection_(connection),
         num_send_completed_(0),
-        num_gatt_services_unavailable_events_(0),
         delete_on_disconnect_(false),
         delete_on_message_sent_(false) {}
 
@@ -280,10 +279,6 @@
 
   int num_send_completed() { return num_send_completed_; }
 
-  int num_gatt_services_unavailable_events() {
-    return num_gatt_services_unavailable_events_;
-  }
-
   bool delete_on_disconnect() { return delete_on_disconnect_; }
 
   void set_delete_on_disconnect(bool delete_on_disconnect) {
@@ -316,16 +311,11 @@
       delete connection_;
   }
 
-  void OnGattCharacteristicsNotAvailable() override {
-    ++num_gatt_services_unavailable_events_;
-  }
-
  private:
   Connection* connection_;
   std::string last_deserialized_message_;
   bool last_send_success_;
   int num_send_completed_;
-  int num_gatt_services_unavailable_events_;
   bool delete_on_disconnect_;
   bool delete_on_message_sent_;
 };
@@ -347,7 +337,6 @@
     generator_ = nullptr;
     receiver_ = nullptr;
     has_verified_connection_result_ = false;
-    has_verified_gatt_services_event_ = false;
     connection_observer_.reset();
 
     adapter_ = base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>();
@@ -386,11 +375,6 @@
   }
 
   void TearDown() override {
-    ASSERT_TRUE(has_verified_connection_result_);
-    if (connection_observer_ && !has_verified_gatt_services_event_) {
-      EXPECT_EQ(0,
-                connection_observer_->num_gatt_services_unavailable_events());
-    }
     connection_observer_.reset();
   }
 
@@ -634,11 +618,6 @@
     has_verified_connection_result_ = true;
   }
 
-  void VerifyGattServicesUnavailableEventSent() {
-    EXPECT_EQ(1, connection_observer_->num_gatt_services_unavailable_events());
-    has_verified_gatt_services_event_ = true;
-  }
-
   BluetoothLowEnergyWeaveClientConnection::GattServiceOperationResult
   GattServiceOperationResultSuccessOrFailure(bool success) {
     return success ? BluetoothLowEnergyWeaveClientConnection::
@@ -668,7 +647,6 @@
   base::MessageLoop message_loop_;
   bool last_wire_message_success_;
   bool has_verified_connection_result_;
-  bool has_verified_gatt_services_event_;
   NiceMock<MockBluetoothLowEnergyWeavePacketGenerator>* generator_;
   NiceMock<MockBluetoothLowEnergyWeavePacketReceiver>* receiver_;
   std::unique_ptr<MockConnectionObserver> connection_observer_;
@@ -853,7 +831,6 @@
   EXPECT_EQ(connection->sub_status(), SubStatus::DISCONNECTED);
   EXPECT_EQ(connection->status(), Connection::Status::DISCONNECTED);
 
-  VerifyGattServicesUnavailableEventSent();
   VerifyBleWeaveConnectionResult(
       BluetoothLowEnergyWeaveClientConnection::BleWeaveConnectionResult::
           BLE_WEAVE_CONNECTION_RESULT_ERROR_FINDING_GATT_CHARACTERISTICS);
@@ -880,7 +857,6 @@
   EXPECT_EQ(connection->sub_status(), SubStatus::DISCONNECTED);
   EXPECT_EQ(connection->status(), Connection::Status::DISCONNECTED);
 
-  VerifyGattServicesUnavailableEventSent();
   VerifyBleWeaveConnectionResult(
       BluetoothLowEnergyWeaveClientConnection::BleWeaveConnectionResult::
           BLE_WEAVE_CONNECTION_RESULT_ERROR_GATT_CHARACTERISTIC_NOT_AVAILABLE);
@@ -1509,7 +1485,6 @@
   EXPECT_EQ(connection->sub_status(), SubStatus::DISCONNECTED);
   EXPECT_EQ(connection->status(), Connection::Status::DISCONNECTED);
 
-  VerifyGattServicesUnavailableEventSent();
   VerifyBleWeaveConnectionResult(
       BluetoothLowEnergyWeaveClientConnection::BleWeaveConnectionResult::
           BLE_WEAVE_CONNECTION_RESULT_TIMEOUT_FINDING_GATT_CHARACTERISTICS);
diff --git a/components/cryptauth/connection.cc b/components/cryptauth/connection.cc
index 84158ff..b16adc99 100644
--- a/components/cryptauth/connection.cc
+++ b/components/cryptauth/connection.cc
@@ -104,11 +104,6 @@
   return WireMessage::Deserialize(received_bytes_, is_incomplete_message);
 }
 
-void Connection::NotifyGattCharacteristicsNotAvailable() {
-  for (auto& observer : observers_)
-    observer.OnGattCharacteristicsNotAvailable();
-}
-
 std::string Connection::GetDeviceInfoLogString() {
   std::stringstream ss;
   ss << "{id: \"" << remote_device().GetTruncatedDeviceIdForLogs()
diff --git a/components/cryptauth/connection.h b/components/cryptauth/connection.h
index 8429804..b325db9 100644
--- a/components/cryptauth/connection.h
+++ b/components/cryptauth/connection.h
@@ -91,8 +91,6 @@
   virtual std::unique_ptr<WireMessage> DeserializeWireMessage(
       bool* is_incomplete_message);
 
-  void NotifyGattCharacteristicsNotAvailable();
-
   // Returns a string describing the associated device for logging purposes.
   std::string GetDeviceInfoLogString();
 
diff --git a/components/cryptauth/connection_observer.h b/components/cryptauth/connection_observer.h
index 566eb684..3ac1a99c 100644
--- a/components/cryptauth/connection_observer.h
+++ b/components/cryptauth/connection_observer.h
@@ -32,13 +32,6 @@
   virtual void OnSendCompleted(const Connection& connection,
                                const WireMessage& message,
                                bool success) {}
-
-  // Called when GATT characteristics are not available. This observer function
-  // is a temporary work-around (see crbug.com/784968).
-  // TODO(khorimoto): This observer function is specific to only one Connection
-  //     implementation, so it is hacky to include it as part of the observer
-  //     for Connection. Remove this work-around when it is no longer necessary.
-  virtual void OnGattCharacteristicsNotAvailable() {}
 };
 
 }  // namespace cryptauth
diff --git a/components/cryptauth/fake_connection.cc b/components/cryptauth/fake_connection.cc
index d68d600..34e6828 100644
--- a/components/cryptauth/fake_connection.cc
+++ b/components/cryptauth/fake_connection.cc
@@ -82,10 +82,6 @@
   pending_payload_.clear();
 }
 
-void FakeConnection::NotifyGattCharacteristicsNotAvailable() {
-  Connection::NotifyGattCharacteristicsNotAvailable();
-}
-
 void FakeConnection::SendMessageImpl(std::unique_ptr<WireMessage> message) {
   CHECK(!current_message_);
   current_message_ = std::move(message);
diff --git a/components/cryptauth/fake_connection.h b/components/cryptauth/fake_connection.h
index 981599d..f89cdc86 100644
--- a/components/cryptauth/fake_connection.h
+++ b/components/cryptauth/fake_connection.h
@@ -39,9 +39,6 @@
   // container WireMessage format.
   void ReceiveMessage(const std::string& feature, const std::string& payload);
 
-  // Notifies observers that GATT characteristics are unavailable.
-  void NotifyGattCharacteristicsNotAvailable();
-
   // Returns the current message in progress of being sent.
   WireMessage* current_message() { return current_message_.get(); }
 
diff --git a/components/cryptauth/fake_secure_channel.cc b/components/cryptauth/fake_secure_channel.cc
index edc5a50..720baa1 100644
--- a/components/cryptauth/fake_secure_channel.cc
+++ b/components/cryptauth/fake_secure_channel.cc
@@ -44,13 +44,6 @@
     observer->OnMessageSent(this, sequence_number);
 }
 
-void FakeSecureChannel::NotifyGattCharacteristicsNotAvailable() {
-  // Copy to prevent channel from being removed during handler.
-  std::vector<Observer*> observers_copy = observers_;
-  for (auto* observer : observers_copy)
-    observer->OnGattCharacteristicsNotAvailable();
-}
-
 void FakeSecureChannel::Initialize() {
   ChangeStatus(Status::CONNECTING);
 }
diff --git a/components/cryptauth/fake_secure_channel.h b/components/cryptauth/fake_secure_channel.h
index 5213590..f7ec0e4 100644
--- a/components/cryptauth/fake_secure_channel.h
+++ b/components/cryptauth/fake_secure_channel.h
@@ -26,7 +26,6 @@
   void ChangeStatus(const Status& new_status);
   void ReceiveMessage(const std::string& feature, const std::string& payload);
   void CompleteSendingMessage(int sequence_number);
-  void NotifyGattCharacteristicsNotAvailable();
 
   std::vector<Observer*> observers() { return observers_; }
 
diff --git a/components/cryptauth/secure_channel.cc b/components/cryptauth/secure_channel.cc
index 1b6746c..b7a516a 100644
--- a/components/cryptauth/secure_channel.cc
+++ b/components/cryptauth/secure_channel.cc
@@ -212,15 +212,6 @@
   Disconnect();
 }
 
-void SecureChannel::OnGattCharacteristicsNotAvailable() {
-  NotifyGattCharacteristicsNotAvailable();
-}
-
-void SecureChannel::NotifyGattCharacteristicsNotAvailable() {
-  for (auto& observer : observer_list_)
-    observer.OnGattCharacteristicsNotAvailable();
-}
-
 void SecureChannel::TransitionToStatus(const Status& new_status) {
   if (new_status == status_) {
     // Only report changes to state.
diff --git a/components/cryptauth/secure_channel.h b/components/cryptauth/secure_channel.h
index 32267af..0495a55 100644
--- a/components/cryptauth/secure_channel.h
+++ b/components/cryptauth/secure_channel.h
@@ -63,14 +63,6 @@
     // corresponds to the value returned by an earlier call to SendMessage().
     virtual void OnMessageSent(SecureChannel* secure_channel,
                                int sequence_number) {}
-
-    // Called when GATT characteristics are not available. This observer
-    // function is a temporary work-around (see crbug.com/784968).
-    // TODO(khorimoto): This observer function is specific to only one
-    //     SecureChannel implementation, so it is hacky to include it as part of
-    //     the observer for SecureChannel. Remove this work-around when it is no
-    //     longer necessary.
-    virtual void OnGattCharacteristicsNotAvailable() {}
   };
 
   class Factory {
@@ -116,13 +108,10 @@
   void OnSendCompleted(const cryptauth::Connection& connection,
                        const cryptauth::WireMessage& wire_message,
                        bool success) override;
-  void OnGattCharacteristicsNotAvailable() override;
 
  protected:
   SecureChannel(std::unique_ptr<Connection> connection);
 
-  void NotifyGattCharacteristicsNotAvailable();
-
   Status status_;
 
  private:
diff --git a/components/cryptauth/secure_channel_unittest.cc b/components/cryptauth/secure_channel_unittest.cc
index a02fa5f..4154c2a 100644
--- a/components/cryptauth/secure_channel_unittest.cc
+++ b/components/cryptauth/secure_channel_unittest.cc
@@ -68,10 +68,6 @@
     return sent_sequence_numbers_;
   }
 
-  int num_gatt_services_unavailable_events() {
-    return num_gatt_services_unavailable_events_;
-  }
-
   // SecureChannel::Observer:
   void OnSecureChannelStatusChanged(
       SecureChannel* secure_channel,
@@ -95,16 +91,11 @@
     sent_sequence_numbers_.push_back(sequence_number);
   }
 
-  void OnGattCharacteristicsNotAvailable() override {
-    ++num_gatt_services_unavailable_events_;
-  }
-
  private:
   SecureChannel* secure_channel_;
   std::vector<SecureChannelStatusChange> connection_status_changes_;
   std::vector<ReceivedMessage> received_messages_;
   std::vector<int> sent_sequence_numbers_;
-  int num_gatt_services_unavailable_events_ = 0;
 };
 
 // Observer used in the ObserverDeletesChannel test. This Observer deletes the
@@ -171,8 +162,6 @@
         weak_ptr_factory_(this) {}
 
   void SetUp() override {
-    has_verified_gatt_services_event_ = false;
-
     test_authenticator_factory_ = std::make_unique<TestAuthenticatorFactory>();
     DeviceToDeviceAuthenticator::Factory::SetInstanceForTesting(
         test_authenticator_factory_.get());
@@ -209,9 +198,6 @@
     if (secure_channel_)
       VerifyNoMessageBeingSent();
 
-    if (!has_verified_gatt_services_event_)
-      EXPECT_EQ(0, test_observer_->num_gatt_services_unavailable_events());
-
     cryptauth::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
         nullptr);
   }
@@ -359,13 +345,6 @@
     EXPECT_EQ(expected_payload, wire_message->payload());
   }
 
-  void VerifyGattServicesUnavailableEvent() {
-    EXPECT_EQ(1, test_observer_->num_gatt_services_unavailable_events());
-    has_verified_gatt_services_event_ = true;
-  }
-
-  bool has_verified_gatt_services_event_;
-
   // Owned by secure_channel_.
   FakeConnection* fake_connection_;
 
@@ -411,21 +390,6 @@
   });
 }
 
-TEST_F(CryptAuthSecureChannelTest, GattServicesUnavailable) {
-  secure_channel_->Initialize();
-  VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
-      {SecureChannel::Status::DISCONNECTED,
-       SecureChannel::Status::CONNECTING}});
-
-  fake_connection_->NotifyGattCharacteristicsNotAvailable();
-  VerifyGattServicesUnavailableEvent();
-
-  fake_connection_->CompleteInProgressConnection(/* success */ false);
-  VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange>{
-      {SecureChannel::Status::CONNECTING,
-       SecureChannel::Status::DISCONNECTED}});
-}
-
 TEST_F(CryptAuthSecureChannelTest, DisconnectBeforeAuthentication) {
   secure_channel_->Initialize();
   VerifyConnectionStateChanges(std::vector<SecureChannelStatusChange> {
diff --git a/components/mirroring/service/BUILD.gn b/components/mirroring/service/BUILD.gn
index a5b3ef6..fef30b2b 100644
--- a/components/mirroring/service/BUILD.gn
+++ b/components/mirroring/service/BUILD.gn
@@ -31,6 +31,8 @@
     "mirror_settings.h",
     "receiver_response.cc",
     "receiver_response.h",
+    "remoting_sender.cc",
+    "remoting_sender.h",
     "rtp_stream.cc",
     "rtp_stream.h",
     "session.cc",
@@ -62,6 +64,7 @@
     "//media/cast:net",
     "//media/cast:sender",
     "//media/mojo/common:common",
+    "//media/mojo/interfaces:remoting",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//net",
@@ -80,6 +83,7 @@
     "fake_video_capture_host.h",
     "message_dispatcher_unittest.cc",
     "receiver_response_unittest.cc",
+    "remoting_sender_unittest.cc",
     "rtp_stream_unittest.cc",
     "session_monitor_unittest.cc",
     "session_unittest.cc",
@@ -100,6 +104,7 @@
     "//media/cast:sender",
     "//media/cast:test_support",
     "//media/cast:test_support",
+    "//media/mojo/interfaces:remoting",
     "//mojo/public/cpp/bindings",
     "//net",
     "//services/network:test_support",
diff --git a/components/mirroring/service/remoting_sender.cc b/components/mirroring/service/remoting_sender.cc
new file mode 100644
index 0000000..11b21e4e
--- /dev/null
+++ b/components/mirroring/service/remoting_sender.cc
@@ -0,0 +1,216 @@
+// 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.
+
+#include "components/mirroring/service/remoting_sender.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/time/default_tick_clock.h"
+#include "media/cast/constants.h"
+#include "media/cast/sender/sender_encoded_frame.h"
+#include "media/mojo/common/mojo_data_pipe_read_write.h"
+
+namespace mirroring {
+
+RemotingSender::RemotingSender(
+    scoped_refptr<media::cast::CastEnvironment> cast_environment,
+    media::cast::CastTransport* transport,
+    const media::cast::FrameSenderConfig& config,
+    mojo::ScopedDataPipeConsumerHandle pipe,
+    media::mojom::RemotingDataStreamSenderRequest request,
+    base::OnceClosure error_callback)
+    : FrameSender(cast_environment,
+                  transport,
+                  config,
+                  media::cast::NewFixedCongestionControl(config.max_bitrate)),
+      clock_(cast_environment->Clock()),
+      error_callback_(std::move(error_callback)),
+      data_pipe_reader_(new media::MojoDataPipeReader(std::move(pipe))),
+      binding_(this, std::move(request)),
+      input_queue_discards_remaining_(0),
+      is_reading_(false),
+      flow_restart_pending_(true),
+      weak_factory_(this) {
+  binding_.set_connection_error_handler(base::BindOnce(
+      &RemotingSender::OnRemotingDataStreamError, base::Unretained(this)));
+}
+
+RemotingSender::~RemotingSender() {}
+
+void RemotingSender::SendFrame(uint32_t frame_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const bool need_to_start_processing = input_queue_.empty();
+  input_queue_.push(base::BindRepeating(&RemotingSender::ReadFrame,
+                                        base::Unretained(this), frame_size));
+  input_queue_.push(base::BindRepeating(&RemotingSender::TrySendFrame,
+                                        base::Unretained(this)));
+  if (need_to_start_processing)
+    ProcessNextInputTask();
+}
+
+void RemotingSender::CancelInFlightData() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+// TODO(miu): The following code is something we want to do as an
+// optimization. However, as-is, it's not quite correct. We can only cancel
+// frames where no packets have actually hit the network yet. Said another
+// way, we can only cancel frames the receiver has definitely not seen any
+// part of (including kickstarting!). http://crbug.com/647423
+#if 0
+  if (latest_acked_frame_id_ < last_sent_frame_id_) {
+    std::vector<media::cast::FrameId> frames_to_cancel;
+    do {
+      ++latest_acked_frame_id_;
+      frames_to_cancel.push_back(latest_acked_frame_id_);
+    } while (latest_acked_frame_id_ < last_sent_frame_id_);
+    transport_->CancelSendingFrames(ssrc_, frames_to_cancel);
+  }
+#endif
+
+  // Flag that all pending input operations should discard data.
+  input_queue_discards_remaining_ = input_queue_.size();
+
+  flow_restart_pending_ = true;
+  VLOG(1) << "Now restarting because in-flight data was just canceled.";
+}
+
+int RemotingSender::GetNumberOfFramesInEncoder() const {
+  NOTREACHED();
+  return 0;
+}
+
+base::TimeDelta RemotingSender::GetInFlightMediaDuration() const {
+  NOTREACHED();
+  return base::TimeDelta();
+}
+
+void RemotingSender::OnCancelSendingFrames() {
+  // One or more frames were canceled. This may allow pending input operations
+  // to complete.
+  ProcessNextInputTask();
+}
+
+void RemotingSender::ProcessNextInputTask() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (input_queue_.empty() || is_reading_)
+    return;
+
+  input_queue_.front().Run();
+}
+
+void RemotingSender::ReadFrame(uint32_t size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!is_reading_);
+  if (!data_pipe_reader_->IsPipeValid()) {
+    VLOG(1) << "Data pipe handle no longer valid.";
+    OnRemotingDataStreamError();
+    return;
+  }
+
+  is_reading_ = true;
+  if (input_queue_discards_remaining_ > 0) {
+    data_pipe_reader_->Read(
+        nullptr, size,
+        base::BindOnce(&RemotingSender::OnFrameRead, base::Unretained(this)));
+  } else {
+    next_frame_data_.resize(size);
+    data_pipe_reader_->Read(
+        reinterpret_cast<uint8_t*>(base::data(next_frame_data_)), size,
+        base::BindOnce(&RemotingSender::OnFrameRead, base::Unretained(this)));
+  }
+}
+
+void RemotingSender::TrySendFrame() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!is_reading_);
+  if (input_queue_discards_remaining_ > 0) {
+    OnInputTaskComplete();
+    return;
+  }
+
+  // If there would be too many frames in-flight, do not proceed.
+  if (GetUnacknowledgedFrameCount() >= media::cast::kMaxUnackedFrames) {
+    VLOG(1) << "Cannot send frame now because too many frames are in flight.";
+    return;
+  }
+
+  const bool is_first_frame_to_be_sent = last_send_time_.is_null();
+  const media::cast::FrameId frame_id = is_first_frame_to_be_sent
+                                            ? media::cast::FrameId::first()
+                                            : (last_sent_frame_id_ + 1);
+
+  base::TimeTicks last_frame_reference_time = last_send_time_;
+  auto remoting_frame = std::make_unique<media::cast::SenderEncodedFrame>();
+  remoting_frame->frame_id = frame_id;
+  if (flow_restart_pending_) {
+    remoting_frame->dependency = media::cast::EncodedFrame::KEY;
+    flow_restart_pending_ = false;
+  } else {
+    DCHECK(!is_first_frame_to_be_sent);
+    remoting_frame->dependency = media::cast::EncodedFrame::DEPENDENT;
+  }
+  remoting_frame->referenced_frame_id =
+      remoting_frame->dependency == media::cast::EncodedFrame::KEY
+          ? frame_id
+          : frame_id - 1;
+  remoting_frame->reference_time = clock_->NowTicks();
+  remoting_frame->encode_completion_time = remoting_frame->reference_time;
+  media::cast::RtpTimeTicks last_frame_rtp_timestamp;
+  if (is_first_frame_to_be_sent) {
+    last_frame_reference_time = remoting_frame->reference_time;
+    last_frame_rtp_timestamp =
+        media::cast::RtpTimeTicks() - media::cast::RtpTimeDelta::FromTicks(1);
+  } else {
+    last_frame_rtp_timestamp = GetRecordedRtpTimestamp(frame_id - 1);
+  }
+  // Ensure each successive frame's RTP timestamp is unique, but otherwise just
+  // base it on the reference time.
+  remoting_frame->rtp_timestamp =
+      last_frame_rtp_timestamp +
+      std::max(media::cast::RtpTimeDelta::FromTicks(1),
+               media::cast::RtpTimeDelta::FromTimeDelta(
+                   remoting_frame->reference_time - last_frame_reference_time,
+                   media::cast::kRemotingRtpTimebase));
+  remoting_frame->data.swap(next_frame_data_);
+
+  SendEncodedFrame(0, std::move(remoting_frame));
+
+  OnInputTaskComplete();
+}
+
+void RemotingSender::OnFrameRead(bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(is_reading_);
+  is_reading_ = false;
+  if (!success) {
+    OnRemotingDataStreamError();
+    return;
+  }
+  OnInputTaskComplete();
+}
+
+void RemotingSender::OnInputTaskComplete() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!input_queue_.empty());
+  input_queue_.pop();
+  if (input_queue_discards_remaining_ > 0)
+    --input_queue_discards_remaining_;
+
+  // Always force a post task to prevent the stack from growing too deep.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&RemotingSender::ProcessNextInputTask,
+                                weak_factory_.GetWeakPtr()));
+}
+
+void RemotingSender::OnRemotingDataStreamError() {
+  data_pipe_reader_.reset();
+  binding_.Close();
+  if (!error_callback_.is_null())
+    std::move(error_callback_).Run();
+}
+
+}  // namespace mirroring
diff --git a/components/mirroring/service/remoting_sender.h b/components/mirroring/service/remoting_sender.h
new file mode 100644
index 0000000..d36d005
--- /dev/null
+++ b/components/mirroring/service/remoting_sender.h
@@ -0,0 +1,120 @@
+// 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_REMOTING_SENDER_H_
+#define COMPONENTS_MIRRORING_SERVICE_REMOTING_SENDER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "media/cast/sender/frame_sender.h"
+#include "media/mojo/interfaces/remoting.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace base {
+class TickClock;
+}  // namespace base
+
+namespace media {
+class MojoDataPipeReader;
+}  // namespace media
+
+namespace mirroring {
+
+// RTP sender for a single Cast Remoting RTP stream. The client calls Send() to
+// instruct the sender to read from a Mojo data pipe and transmit the data using
+// a CastTransport.
+class RemotingSender final : public media::mojom::RemotingDataStreamSender,
+                             public media::cast::FrameSender {
+ public:
+  // |transport| is expected to outlive this class.
+  RemotingSender(scoped_refptr<media::cast::CastEnvironment> cast_environment,
+                 media::cast::CastTransport* transport,
+                 const media::cast::FrameSenderConfig& config,
+                 mojo::ScopedDataPipeConsumerHandle pipe,
+                 media::mojom::RemotingDataStreamSenderRequest request,
+                 base::OnceClosure error_callback);
+  ~RemotingSender() override;
+
+ private:
+  // Friend class for unit tests.
+  friend class RemotingSenderTest;
+
+  // media::mojom::RemotingDataStreamSender implementation. SendFrame() will
+  // push callbacks onto the back of the input queue, and these may or may not
+  // be processed at a later time. It depends on whether the data pipe has data
+  // available or the CastTransport can accept more frames. CancelInFlightData()
+  // is processed immediately, and will cause all pending operations to discard
+  // data when they are processed later.
+  void SendFrame(uint32_t frame_size) override;
+  void CancelInFlightData() override;
+
+  // FrameSender override.
+  int GetNumberOfFramesInEncoder() const override;
+  base::TimeDelta GetInFlightMediaDuration() const override;
+  void OnCancelSendingFrames() override;
+
+  // Attempt to run next pending input task, popping the head of the input queue
+  // as each task succeeds.
+  void ProcessNextInputTask();
+
+  // These are called via callbacks run from the input queue.
+  // Consumes a frame of |size| from the associated Mojo data pipe.
+  void ReadFrame(uint32_t size);
+  // Sends out the frame to the receiver over network.
+  void TrySendFrame();
+
+  // Called when a frame is completely read/discarded from the data pipe.
+  void OnFrameRead(bool success);
+
+  // Called when an input task completes.
+  void OnInputTaskComplete();
+
+  void OnRemotingDataStreamError();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  const base::TickClock* clock_;
+
+  // Callback that is run to notify when a fatal error occurs.
+  base::OnceClosure error_callback_;
+
+  std::unique_ptr<media::MojoDataPipeReader> data_pipe_reader_;
+
+  // Mojo binding for this instance. Implementation at the other end of the
+  // message pipe uses the RemotingDataStreamSender interface to control when
+  // this RemotingSender consumes from |pipe_|.
+  mojo::Binding<media::mojom::RemotingDataStreamSender> binding_;
+
+  // The next frame's payload data. Populated by call to OnFrameRead() when
+  // reading succeeded.
+  std::string next_frame_data_;
+
+  // Queue of pending input operations. |input_queue_discards_remaining_|
+  // indicates the number of operations where data should be discarded (due to
+  // CancelInFlightData()).
+  base::queue<base::RepeatingClosure> input_queue_;
+  size_t input_queue_discards_remaining_;
+
+  // Indicates whether the |data_pipe_reader_| is processing a reading request.
+  bool is_reading_;
+
+  // Set to true if the first frame has not yet been sent, or if a
+  // CancelInFlightData() operation just completed. This causes TrySendFrame()
+  // to mark the next frame as the start of a new sequence.
+  bool flow_restart_pending_;
+
+  // NOTE: Weak pointers must be invalidated before all other member variables.
+  base::WeakPtrFactory<RemotingSender> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RemotingSender);
+};
+
+}  // namespace mirroring
+
+#endif  // COMPONENTS_MIRRORING_SERVICE_REMOTING_SENDER_H_
diff --git a/components/mirroring/service/remoting_sender_unittest.cc b/components/mirroring/service/remoting_sender_unittest.cc
new file mode 100644
index 0000000..0c61f9d9
--- /dev/null
+++ b/components/mirroring/service/remoting_sender_unittest.cc
@@ -0,0 +1,615 @@
+// 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.
+
+#include "components/mirroring/service/remoting_sender.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/time/default_tick_clock.h"
+#include "media/cast/constants.h"
+#include "media/cast/net/cast_transport.h"
+#include "media/cast/test/utility/default_config.h"
+#include "media/mojo/interfaces/remoting.mojom.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mirroring {
+
+namespace {
+
+// Data pipe capacity is 1KB.
+constexpr int kDataPipeCapacity = 1024;
+
+// Implements the CastTransport interface to capture output from the
+// RemotingSender.
+class FakeTransport : public media::cast::CastTransport {
+ public:
+  FakeTransport() {}
+  ~FakeTransport() final {}
+
+  void TakeSentFrames(std::vector<media::cast::EncodedFrame>* frames) {
+    frames->swap(sent_frames_);
+    sent_frames_.clear();
+  }
+
+  void TakeCanceledFrameIds(std::vector<media::cast::FrameId>* frame_ids) {
+    frame_ids->swap(canceled_frame_ids_);
+    canceled_frame_ids_.clear();
+  }
+
+  media::cast::FrameId WaitForKickstart() {
+    base::RunLoop run_loop;
+    kickstarted_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+    return kickstarted_frame_id_;
+  }
+
+ protected:
+  void InsertFrame(uint32_t ssrc,
+                   const media::cast::EncodedFrame& frame) final {
+    sent_frames_.push_back(frame);
+  }
+
+  void CancelSendingFrames(
+      uint32_t ssrc,
+      const std::vector<media::cast::FrameId>& frame_ids) final {
+    for (media::cast::FrameId frame_id : frame_ids)
+      canceled_frame_ids_.push_back(frame_id);
+  }
+
+  void ResendFrameForKickstart(uint32_t ssrc,
+                               media::cast::FrameId frame_id) final {
+    kickstarted_frame_id_ = frame_id;
+    if (!kickstarted_callback_.is_null())
+      base::ResetAndReturn(&kickstarted_callback_).Run();
+  }
+
+  // The rest of the interface is not used for these tests.
+  void SendSenderReport(
+      uint32_t ssrc,
+      base::TimeTicks current_time,
+      media::cast::RtpTimeTicks current_time_as_rtp_timestamp) final {}
+  void AddValidRtpReceiver(uint32_t rtp_sender_ssrc,
+                           uint32_t rtp_receiver_ssrc) final {}
+  void InitializeRtpReceiverRtcpBuilder(
+      uint32_t rtp_receiver_ssrc,
+      const media::cast::RtcpTimeData& time_data) final {}
+  void AddCastFeedback(const media::cast::RtcpCastMessage& cast_message,
+                       base::TimeDelta target_delay) final {}
+  void AddPli(const media::cast::RtcpPliMessage& pli_message) final {}
+  void AddRtcpEvents(
+      const media::cast::ReceiverRtcpEventSubscriber::RtcpEvents& e) final {}
+  void AddRtpReceiverReport(const media::cast::RtcpReportBlock& b) final {}
+  void SendRtcpFromRtpReceiver() final {}
+  void SetOptions(const base::DictionaryValue& options) final {}
+
+ private:
+  std::vector<media::cast::EncodedFrame> sent_frames_;
+  std::vector<media::cast::FrameId> canceled_frame_ids_;
+
+  base::RepeatingClosure kickstarted_callback_;
+  media::cast::FrameId kickstarted_frame_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeTransport);
+};
+
+}  // namespace
+
+class RemotingSenderTest : public ::testing::Test {
+ protected:
+  RemotingSenderTest()
+      : cast_environment_(new media::cast::CastEnvironment(
+            base::DefaultTickClock::GetInstance(),
+            scoped_task_environment_.GetMainThreadTaskRunner(),
+            scoped_task_environment_.GetMainThreadTaskRunner(),
+            scoped_task_environment_.GetMainThreadTaskRunner())),
+        expecting_error_callback_run_(false),
+        receiver_ssrc_(-1) {
+    const MojoCreateDataPipeOptions data_pipe_options{
+        sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1,
+        kDataPipeCapacity};
+    mojo::ScopedDataPipeConsumerHandle consumer_end;
+    CHECK_EQ(MOJO_RESULT_OK,
+             mojo::CreateDataPipe(&data_pipe_options, &producer_end_,
+                                  &consumer_end));
+
+    media::cast::FrameSenderConfig video_config =
+        media::cast::GetDefaultVideoSenderConfig();
+    video_config.rtp_payload_type = media::cast::RtpPayloadType::REMOTE_VIDEO;
+    video_config.codec = media::cast::CODEC_VIDEO_REMOTE;
+    receiver_ssrc_ = video_config.receiver_ssrc;
+    remoting_sender_ = std::make_unique<RemotingSender>(
+        cast_environment_, &transport_, video_config, std::move(consumer_end),
+        mojo::MakeRequest(&sender_),
+        base::BindOnce(
+            [](bool expecting_error_callback_run) {
+              CHECK(expecting_error_callback_run);
+            },
+            expecting_error_callback_run_));
+
+    // Give CastRemotingSender a small RTT measurement to prevent kickstart
+    // testing from taking too long.
+    remoting_sender_->OnMeasuredRoundTripTime(
+        base::TimeDelta::FromMilliseconds(1));
+    RunPendingTasks();
+  }
+
+  ~RemotingSenderTest() override {}
+
+  void TearDown() final {
+    remoting_sender_.reset();
+    // Allow any pending tasks to run before destruction.
+    RunPendingTasks();
+  }
+
+  // Allow pending tasks, such as Mojo method calls, to execute.
+  void RunPendingTasks() { scoped_task_environment_.RunUntilIdle(); }
+
+ protected:
+  media::cast::FrameId latest_acked_frame_id() const {
+    return remoting_sender_->latest_acked_frame_id_;
+  }
+
+  int NumberOfFramesInFlight() {
+    return remoting_sender_->GetUnacknowledgedFrameCount();
+  }
+
+  size_t GetSizeOfNextFrameData() {
+    return remoting_sender_->next_frame_data_.size();
+  }
+
+  bool IsFlowRestartPending() const {
+    return remoting_sender_->flow_restart_pending_;
+  }
+
+  bool ProduceDataChunk(size_t offset, size_t size) WARN_UNUSED_RESULT {
+    std::vector<uint8_t> fake_chunk(size);
+    for (size_t i = 0; i < size; ++i)
+      fake_chunk[i] = static_cast<uint8_t>(offset + i);
+    uint32_t num_bytes = fake_chunk.size();
+    return producer_end_->WriteData(fake_chunk.data(), &num_bytes,
+                                    MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) ==
+           MOJO_RESULT_OK;
+  }
+
+  void PostMojoCallTask_SendFrame(uint32_t size) { sender_->SendFrame(size); }
+
+  void PostMojoCallTask_CancelInFlightData() { sender_->CancelInFlightData(); }
+
+  void TakeSentFrames(std::vector<media::cast::EncodedFrame>* frames) {
+    transport_.TakeSentFrames(frames);
+  }
+
+  bool ExpectOneFrameWasSent(size_t expected_payload_size) {
+    std::vector<media::cast::EncodedFrame> frames;
+    transport_.TakeSentFrames(&frames);
+    EXPECT_EQ(1u, frames.size());
+    if (frames.empty())
+      return false;
+    return ExpectCorrectFrameData(expected_payload_size, frames.front());
+  }
+
+  void AckUpToAndIncluding(media::cast::FrameId frame_id) {
+    media::cast::RtcpCastMessage cast_feedback(receiver_ssrc_);
+    cast_feedback.ack_frame_id = frame_id;
+    remoting_sender_->OnReceivedCastFeedback(cast_feedback);
+  }
+
+  void AckOldestInFlightFrames(int count) {
+    AckUpToAndIncluding(latest_acked_frame_id() + count);
+  }
+
+  // Blocks the caller indefinitely, until a kickstart frame is sent, and then
+  // returns the FrameId of the kickstarted-frame.
+  media::cast::FrameId WaitForKickstart() {
+    return transport_.WaitForKickstart();
+  }
+
+  bool ExpectNoFramesCanceled() {
+    std::vector<media::cast::FrameId> frame_ids;
+    transport_.TakeCanceledFrameIds(&frame_ids);
+    return frame_ids.empty();
+  }
+
+  bool ExpectFramesCanceled(media::cast::FrameId first_frame_id,
+                            media::cast::FrameId last_frame_id) {
+    std::vector<media::cast::FrameId> frame_ids;
+    transport_.TakeCanceledFrameIds(&frame_ids);
+    auto begin = frame_ids.begin();
+    auto end = frame_ids.end();
+    for (auto fid = first_frame_id; fid <= last_frame_id; ++fid) {
+      auto new_end = std::remove(begin, end, fid);
+      if (new_end == end)
+        return false;
+      end = new_end;
+    }
+    return begin == end;
+  }
+
+  static bool ExpectCorrectFrameData(size_t expected_payload_size,
+                                     const media::cast::EncodedFrame& frame) {
+    if (expected_payload_size != frame.data.size()) {
+      ADD_FAILURE() << "Expected frame data size != frame.data.size(): "
+                    << expected_payload_size << " vs " << frame.data.size();
+      return false;
+    }
+    for (size_t i = 0; i < expected_payload_size; ++i) {
+      if (static_cast<uint8_t>(frame.data[i]) != static_cast<uint8_t>(i)) {
+        ADD_FAILURE() << "Frame data byte mismatch at offset " << i;
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  const scoped_refptr<media::cast::CastEnvironment> cast_environment_;
+  FakeTransport transport_;
+  std::unique_ptr<RemotingSender> remoting_sender_;
+  media::mojom::RemotingDataStreamSenderPtr sender_;
+  mojo::ScopedDataPipeProducerHandle producer_end_;
+  bool expecting_error_callback_run_;
+  uint32_t receiver_ssrc_;
+
+  DISALLOW_COPY_AND_ASSIGN(RemotingSenderTest);
+};
+
+TEST_F(RemotingSenderTest, SendsFramesViaMojoInterface) {
+  // One 256-byte chunk pushed through the data pipe to make one frame.
+  ASSERT_TRUE(ProduceDataChunk(0, 256));
+  PostMojoCallTask_SendFrame(256);
+  RunPendingTasks();
+  EXPECT_TRUE(ExpectOneFrameWasSent(256));
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(media::cast::FrameId::first(), latest_acked_frame_id());
+
+  // Four 256-byte chunks pushed through the data pipe to make one frame.
+  PostMojoCallTask_SendFrame(1024);
+  for (int i = 0; i < 4; ++i) {
+    ASSERT_TRUE(ProduceDataChunk(i * 256, 256));
+  }
+  RunPendingTasks();
+  EXPECT_TRUE(ExpectOneFrameWasSent(1024));
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(media::cast::FrameId::first() + 1, latest_acked_frame_id());
+
+  // 10 differently-sized chunks pushed through the data pipe to make one frame
+  // that is larger than the data pipe's total capacity.
+  PostMojoCallTask_SendFrame(6665);
+  size_t offset = 0;
+  for (int i = 0; i < 10; ++i) {
+    const size_t chunk_size = 500 + i * 37;
+    ASSERT_TRUE(ProduceDataChunk(offset, chunk_size));
+    RunPendingTasks();
+    offset += chunk_size;
+  }
+  RunPendingTasks();
+  EXPECT_TRUE(ExpectOneFrameWasSent(6665));
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(media::cast::FrameId::first() + 2, latest_acked_frame_id());
+}
+
+TEST_F(RemotingSenderTest, SendsMultipleFramesWithDelayedAcks) {
+  // Send 4 frames.
+  for (int i = 0; i < 4; ++i) {
+    ASSERT_TRUE(ProduceDataChunk(0, 16));
+    PostMojoCallTask_SendFrame(16);
+  }
+  RunPendingTasks();
+  EXPECT_EQ(4, NumberOfFramesInFlight());
+  EXPECT_TRUE(ExpectNoFramesCanceled());
+
+  // Ack one frame.
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(3, NumberOfFramesInFlight());
+  EXPECT_TRUE(ExpectFramesCanceled(media::cast::FrameId::first(),
+                                   media::cast::FrameId::first()));
+
+  // Ack all.
+  AckOldestInFlightFrames(3);
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+  EXPECT_TRUE(ExpectFramesCanceled(media::cast::FrameId::first() + 1,
+                                   media::cast::FrameId::first() + 3));
+}
+
+TEST_F(RemotingSenderTest, KickstartsIfAckNotTimely) {
+  // Send first frame and don't Ack it. Expect the first frame to be
+  // kickstarted.
+  ASSERT_TRUE(ProduceDataChunk(0, 16));
+  PostMojoCallTask_SendFrame(16);
+  EXPECT_EQ(media::cast::FrameId::first(), WaitForKickstart());
+  EXPECT_EQ(1, NumberOfFramesInFlight());
+
+  // Send 3 more frames and don't Ack them either. Expect the 4th frame to be
+  // kickstarted.
+  for (int i = 0; i < 3; ++i) {
+    ASSERT_TRUE(ProduceDataChunk(0, 16));
+    PostMojoCallTask_SendFrame(16);
+  }
+  EXPECT_EQ(media::cast::FrameId::first() + 3, WaitForKickstart());
+  EXPECT_EQ(4, NumberOfFramesInFlight());
+
+  // Ack the first two frames and wait for another kickstart (for the 4th frame
+  // again).
+  AckOldestInFlightFrames(2);
+  EXPECT_EQ(2, NumberOfFramesInFlight());
+  EXPECT_EQ(media::cast::FrameId::first() + 3, WaitForKickstart());
+}
+
+TEST_F(RemotingSenderTest, CancelsUnsentFrame) {
+  PostMojoCallTask_SendFrame(16);
+  PostMojoCallTask_SendFrame(32);
+  PostMojoCallTask_CancelInFlightData();
+  EXPECT_EQ(0u, GetSizeOfNextFrameData());
+  ASSERT_TRUE(ProduceDataChunk(0, 16));
+  RunPendingTasks();
+  // The first frame's data should have been read from the data pipe.
+  EXPECT_EQ(16u, GetSizeOfNextFrameData());
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+  ASSERT_TRUE(ProduceDataChunk(0, 32));
+  RunPendingTasks();
+  // The |next_frame_data_| was not updated because the second frame data was
+  // discarded from the data pipe.
+  EXPECT_EQ(16u, GetSizeOfNextFrameData());
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Since no frames were sent, none should have been passed to the
+  // CastTransport, and none should have been canceled.
+  std::vector<media::cast::EncodedFrame> frames;
+  TakeSentFrames(&frames);
+  EXPECT_TRUE(frames.empty());
+  EXPECT_TRUE(ExpectNoFramesCanceled());
+}
+
+// http://crbug.com/647423
+#define MAYBE_CancelsFramesInFlight DISABLED_CancelsFramesInFlight
+TEST_F(RemotingSenderTest, MAYBE_CancelsFramesInFlight) {
+  EXPECT_TRUE(IsFlowRestartPending());
+
+  // Send 10 frames.
+  for (int i = 0; i < 10; ++i) {
+    ASSERT_TRUE(ProduceDataChunk(0, 16));
+    PostMojoCallTask_SendFrame(16);
+  }
+  RunPendingTasks();
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(10, NumberOfFramesInFlight());
+
+  // Ack the first frame.
+  AckOldestInFlightFrames(1);
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(9, NumberOfFramesInFlight());
+  EXPECT_TRUE(ExpectFramesCanceled(media::cast::FrameId::first(),
+                                   media::cast::FrameId::first()));
+
+  // Cancel all in-flight data. This should cause the remaining 9 frames to be
+  // canceled.
+  PostMojoCallTask_CancelInFlightData();
+  RunPendingTasks();
+  EXPECT_TRUE(IsFlowRestartPending());
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+  EXPECT_TRUE(ExpectFramesCanceled(media::cast::FrameId::first() + 1,
+                                   media::cast::FrameId::first() + 9));
+
+  // Send one more frame and ack it.
+  ASSERT_TRUE(ProduceDataChunk(0, 16));
+  PostMojoCallTask_SendFrame(16);
+  RunPendingTasks();
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(1, NumberOfFramesInFlight());
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Check that the dependency metadata was set correctly to indicate a frame
+  // that immediately follows a CancelInFlightData() operation.
+  std::vector<media::cast::EncodedFrame> frames;
+  TakeSentFrames(&frames);
+  ASSERT_EQ(11u, frames.size());
+  for (size_t i = 0; i < 11; ++i) {
+    const media::cast::EncodedFrame& frame = frames[i];
+    EXPECT_EQ(media::cast::FrameId::first() + i, frame.frame_id);
+    if (i == 0 || i == 10)
+      EXPECT_EQ(media::cast::EncodedFrame::KEY, frame.dependency);
+    else
+      EXPECT_EQ(media::cast::EncodedFrame::DEPENDENT, frame.dependency);
+  }
+}
+
+TEST_F(RemotingSenderTest, WaitsForDataBeforeConsumingFromDataPipe) {
+  // Queue up and issue Mojo calls to consume three frames. Since no data has
+  // been pushed into the pipe yet no frames should be sent.
+  for (int i = 0; i < 3; ++i) {
+    PostMojoCallTask_SendFrame(4);
+  }
+  RunPendingTasks();
+  EXPECT_TRUE(IsFlowRestartPending());
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Push the data for one frame into the data pipe. This should trigger input
+  // processing and allow one frame to be sent.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  RunPendingTasks();  // Allow Mojo Watcher to signal CastRemotingSender.
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(1, NumberOfFramesInFlight());
+
+  // Now push the data for the other two frames into the data pipe and expect
+  // two more frames to be sent.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  RunPendingTasks();  // Allow Mojo Watcher to signal CastRemotingSender.
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(3, NumberOfFramesInFlight());
+}
+
+TEST_F(RemotingSenderTest, WaitsForDataThenDiscardsCanceledData) {
+  // Queue up and issue Mojo calls to consume data chunks and send three
+  // frames. Since no data has been pushed into the pipe yet no frames should be
+  // sent.
+  for (int i = 0; i < 3; ++i) {
+    PostMojoCallTask_SendFrame(4);
+  }
+  RunPendingTasks();
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Cancel all in-flight data.
+  PostMojoCallTask_CancelInFlightData();
+  RunPendingTasks();
+
+  // Now, push the data for one frame into the data pipe. Because of the
+  // cancellation, no frames should be sent.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  RunPendingTasks();  // Allow Mojo Watcher to signal CastRemotingSender.
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Now push the data for the other two frames into the data pipe and still no
+  // frames should be sent.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  RunPendingTasks();  // Allow Mojo Watcher to signal CastRemotingSender.
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Now issue calls to send another frame and then push the data for it into
+  // the data pipe. Expect to see the frame gets sent since it was provided
+  // after the CancelInFlightData().
+  PostMojoCallTask_SendFrame(4);
+  RunPendingTasks();
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  RunPendingTasks();  // Allow Mojo Watcher to signal CastRemotingSender.
+  EXPECT_EQ(1, NumberOfFramesInFlight());
+}
+
+TEST_F(RemotingSenderTest, StopsConsumingWhileTooManyFramesAreInFlight) {
+  EXPECT_TRUE(IsFlowRestartPending());
+
+  // Send out the maximum possible number of unacked frames, but don't ack any
+  // yet.
+  for (int i = 0; i < media::cast::kMaxUnackedFrames; ++i) {
+    ASSERT_TRUE(ProduceDataChunk(0, 4));
+    PostMojoCallTask_SendFrame(4);
+  }
+  RunPendingTasks();
+  EXPECT_FALSE(IsFlowRestartPending());
+  EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+  // Note: All frames should have been sent to the Transport, and so
+  // CastRemotingSender's single-frame data buffer should be empty.
+  EXPECT_EQ(0u, GetSizeOfNextFrameData());
+
+  // When the client provides one more frame, CastRemotingSender will begin
+  // queuing input operations instead of sending the the frame to the
+  // CastTransport.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  PostMojoCallTask_SendFrame(4);
+  RunPendingTasks();
+  EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+  // Note: The unsent frame resides in CastRemotingSender's single-frame data
+  // buffer.
+  EXPECT_EQ(4u, GetSizeOfNextFrameData());
+
+  // Ack the the first frame and expect sending to resume, with one more frame
+  // being sent to the CastTransport.
+  AckOldestInFlightFrames(1);
+  EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+  // Note: Only one frame was backlogged, and so CastRemotingSender's
+  // single-frame data buffer should be empty.
+  EXPECT_EQ(0u, GetSizeOfNextFrameData());
+
+  // Attempting to send another frame will once again cause CastRemotingSender
+  // to queue input operations.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  PostMojoCallTask_SendFrame(4);
+  RunPendingTasks();
+  EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+  // Note: Once again, CastRemotingSender's single-frame data buffer contains an
+  // unsent frame.
+  EXPECT_EQ(4u, GetSizeOfNextFrameData());
+
+  // Send more frames: Some number of frames will queue-up inside the Mojo data
+  // pipe (the exact number depends on the data pipe's capacity, and how Mojo
+  // manages memory internally). At some point, attempting to produce and push
+  // another frame will fail because the data pipe is full.
+  int num_frames_in_data_pipe = 0;
+  while (ProduceDataChunk(0, 768)) {
+    ++num_frames_in_data_pipe;
+    PostMojoCallTask_SendFrame(768);
+    RunPendingTasks();
+    EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+    // Note: CastRemotingSender's single-frame data buffer should still contain
+    // the unsent 4-byte frame.
+    EXPECT_EQ(4u, GetSizeOfNextFrameData());
+  }
+  EXPECT_LT(0, num_frames_in_data_pipe);
+
+  // Ack one frame at a time until the backlog in the Mojo data pipe has
+  // cleared.
+  int remaining_frames_in_data_pipe = num_frames_in_data_pipe;
+  while (remaining_frames_in_data_pipe > 0) {
+    AckOldestInFlightFrames(1);
+    RunPendingTasks();
+    --remaining_frames_in_data_pipe;
+    EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+    EXPECT_EQ(768u, GetSizeOfNextFrameData());
+  }
+
+  // Ack one more frame. There should no longer be a backlog on the input side
+  // of things.
+  AckOldestInFlightFrames(1);
+  RunPendingTasks();  // No additional Mojo method calls should be made here.
+  EXPECT_EQ(media::cast::kMaxUnackedFrames, NumberOfFramesInFlight());
+  // The single-frame data buffer should be empty to indicate no input backlog.
+  EXPECT_EQ(0u, GetSizeOfNextFrameData());
+
+  // Ack all but one frame.
+  AckOldestInFlightFrames(NumberOfFramesInFlight() - 1);
+  EXPECT_EQ(1, NumberOfFramesInFlight());
+  // ..and one more frame can be sent immediately.
+  ASSERT_TRUE(ProduceDataChunk(0, 4));
+  PostMojoCallTask_SendFrame(4);
+  RunPendingTasks();
+  EXPECT_EQ(2, NumberOfFramesInFlight());
+  // ...and ack these last two frames.
+  AckOldestInFlightFrames(2);
+  EXPECT_EQ(0, NumberOfFramesInFlight());
+
+  // Finally, examine all frames that were sent to the CastTransport, and
+  // confirm their metadata and data is valid.
+  std::vector<media::cast::EncodedFrame> frames;
+  TakeSentFrames(&frames);
+  const size_t total_frames_sent =
+      media::cast::kMaxUnackedFrames + 2 + num_frames_in_data_pipe + 1;
+  ASSERT_EQ(total_frames_sent, frames.size());
+  media::cast::RtpTimeTicks last_rtp_timestamp =
+      media::cast::RtpTimeTicks() - media::cast::RtpTimeDelta::FromTicks(1);
+  for (size_t i = 0; i < total_frames_sent; ++i) {
+    const media::cast::EncodedFrame& frame = frames[i];
+    EXPECT_EQ(media::cast::FrameId::first() + i, frame.frame_id);
+    if (i == 0) {
+      EXPECT_EQ(media::cast::EncodedFrame::KEY, frame.dependency);
+      EXPECT_EQ(media::cast::FrameId::first() + i, frame.referenced_frame_id);
+    } else {
+      EXPECT_EQ(media::cast::EncodedFrame::DEPENDENT, frame.dependency);
+      EXPECT_EQ(media::cast::FrameId::first() + i - 1,
+                frame.referenced_frame_id);
+    }
+
+    // RTP timestamp must be monotonically increasing.
+    EXPECT_GT(frame.rtp_timestamp, last_rtp_timestamp);
+    last_rtp_timestamp = frame.rtp_timestamp;
+
+    size_t expected_frame_size = 4;
+    if ((i >= media::cast::kMaxUnackedFrames + 2u) &&
+        (i < media::cast::kMaxUnackedFrames + 2u + num_frames_in_data_pipe)) {
+      expected_frame_size = 768;
+    }
+    EXPECT_TRUE(ExpectCorrectFrameData(expected_frame_size, frame));
+  }
+}
+
+}  // namespace mirroring
diff --git a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java
index 568e56e..fdda657 100644
--- a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java
+++ b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineItem.java
@@ -97,6 +97,7 @@
     public long receivedBytes;
     public Progress progress;
     public long timeRemainingMs;
+    public boolean isDangerous;
     @FailState
     public int failState;
     @PendingState
diff --git a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java
index fe6efda4..8f826e05 100644
--- a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java
+++ b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/bridges/OfflineItemBridge.java
@@ -50,7 +50,7 @@
             String mimeType, String pageUrl, String originalUrl, boolean isOffTheRecord,
             @OfflineItemState int state, @PendingState int pendingState, boolean isResumable,
             boolean allowMetered, long receivedBytes, long progressValue, long progressMax,
-            @OfflineItemProgressUnit int progressUnit, long timeRemainingMs) {
+            @OfflineItemProgressUnit int progressUnit, long timeRemainingMs, boolean isDangerous) {
         OfflineItem item = new OfflineItem();
         item.id.namespace = nameSpace;
         item.id.id = id;
@@ -78,6 +78,7 @@
         item.progress = new OfflineItem.Progress(
                 progressValue, progressMax == -1 ? null : progressMax, progressUnit);
         item.timeRemainingMs = timeRemainingMs;
+        item.isDangerous = isDangerous;
 
         if (list != null) list.add(item);
         return item;
diff --git a/components/offline_items_collection/core/android/offline_item_bridge.cc b/components/offline_items_collection/core/android/offline_item_bridge.cc
index 0ebf286..19eb372 100644
--- a/components/offline_items_collection/core/android/offline_item_bridge.cc
+++ b/components/offline_items_collection/core/android/offline_item_bridge.cc
@@ -43,7 +43,7 @@
       static_cast<jint>(item.pending_state), item.is_resumable,
       item.allow_metered, item.received_bytes, item.progress.value,
       item.progress.max.value_or(-1), static_cast<jint>(item.progress.unit),
-      item.time_remaining_ms);
+      item.time_remaining_ms, item.is_dangerous);
 }
 
 }  // namespace
diff --git a/components/offline_items_collection/core/offline_item.cc b/components/offline_items_collection/core/offline_item.cc
index 9e7978d..2a31be2 100644
--- a/components/offline_items_collection/core/offline_item.cc
+++ b/components/offline_items_collection/core/offline_item.cc
@@ -53,7 +53,8 @@
       is_resumable(false),
       allow_metered(false),
       received_bytes(0),
-      time_remaining_ms(0) {}
+      time_remaining_ms(0),
+      is_dangerous(false) {}
 
 OfflineItem::OfflineItem(const OfflineItem& other) = default;
 
@@ -86,7 +87,8 @@
          allow_metered == offline_item.allow_metered &&
          received_bytes == offline_item.received_bytes &&
          progress == offline_item.progress &&
-         time_remaining_ms == offline_item.time_remaining_ms;
+         time_remaining_ms == offline_item.time_remaining_ms &&
+         is_dangerous == offline_item.is_dangerous;
 }
 
 OfflineItemVisuals::OfflineItemVisuals() = default;
diff --git a/components/offline_items_collection/core/offline_item.h b/components/offline_items_collection/core/offline_item.h
index 66bf0436..80aec97 100644
--- a/components/offline_items_collection/core/offline_item.h
+++ b/components/offline_items_collection/core/offline_item.h
@@ -180,6 +180,10 @@
   // represents an unknown time remaining.  This field is not used if |state| is
   // COMPLETE.
   int64_t time_remaining_ms;
+
+  // Whether the download might be dangerous and will require additional
+  // validation from user.
+  bool is_dangerous;
 };
 
 // This struct holds any potentially expensive visuals for an OfflineItem.  If
diff --git a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
index 9eb3747..7eefcbe 100644
--- a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
+++ b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
@@ -192,15 +192,6 @@
                               : base::TimeTicks::Now() - defer_time_;
   UMA_HISTOGRAM_TIMES("SubresourceFilter.PageLoad.SafeBrowsingDelay", delay);
 
-  // Log a histogram for the delay we would have introduced if the throttle only
-  // speculatively checks URLs on WillStartRequest. This is only different from
-  // the actual delay if there was at least one redirect.
-  base::TimeDelta no_redirect_speculation_delay =
-      check_results_.size() > 1 ? check_results_.back().check_time : delay;
-  UMA_HISTOGRAM_TIMES(
-      "SubresourceFilter.PageLoad.SafeBrowsingDelay.NoRedirectSpeculation",
-      no_redirect_speculation_delay);
-
   ukm::SourceId source_id = ukm::ConvertToSourceId(
       navigation_handle()->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
   ukm::builders::SubresourceFilter builder(source_id);
diff --git a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
index 611d8c5..a049e4b 100644
--- a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
+++ b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
@@ -60,8 +60,6 @@
 
 const char kSafeBrowsingNavigationDelay[] =
     "SubresourceFilter.PageLoad.SafeBrowsingDelay";
-const char kSafeBrowsingNavigationDelayNoSpeculation[] =
-    "SubresourceFilter.PageLoad.SafeBrowsingDelay.NoRedirectSpeculation";
 const char kSafeBrowsingCheckTime[] =
     "SubresourceFilter.SafeBrowsing.CheckTime";
 const char kActivationListHistogram[] =
@@ -779,7 +777,6 @@
                               static_cast<int>(ActivationList::NONE), 1);
 
   tester().ExpectTotalCount(kSafeBrowsingNavigationDelay, 1);
-  tester().ExpectTotalCount(kSafeBrowsingNavigationDelayNoSpeculation, 1);
   tester().ExpectTotalCount(kSafeBrowsingCheckTime, 1);
 }
 
@@ -845,7 +842,6 @@
   EXPECT_EQ(ActivationLevel::DISABLED,
             *observer()->GetPageActivationForLastCommittedLoad());
   tester().ExpectTotalCount(kSafeBrowsingNavigationDelay, 1);
-  tester().ExpectTotalCount(kSafeBrowsingNavigationDelayNoSpeculation, 1);
   tester().ExpectTotalCount(kSafeBrowsingCheckTime, 1);
 }
 
@@ -869,7 +865,6 @@
 
   tester().ExpectTimeBucketCount(kSafeBrowsingNavigationDelay,
                                  base::TimeDelta::FromMilliseconds(0), 1);
-  tester().ExpectTotalCount(kSafeBrowsingNavigationDelayNoSpeculation, 1);
 }
 
 // Flaky on Win, Chromium and Linux. http://crbug.com/748524
@@ -896,7 +891,6 @@
   const std::string suffix(GetSuffixForList(test_data.activation_list_type));
   tester().ExpectTimeBucketCount(kSafeBrowsingNavigationDelay,
                                  base::TimeDelta::FromMilliseconds(0), 1);
-  tester().ExpectTotalCount(kSafeBrowsingNavigationDelayNoSpeculation, 1);
   tester().ExpectTotalCount(kSafeBrowsingCheckTime, 2);
 }
 
@@ -920,7 +914,6 @@
             *observer()->GetPageActivationForLastCommittedLoad());
   tester().ExpectTimeBucketCount(kSafeBrowsingNavigationDelay,
                                  base::TimeDelta::FromMilliseconds(0), 1);
-  tester().ExpectTotalCount(kSafeBrowsingNavigationDelayNoSpeculation, 1);
 }
 
 TEST_P(SubresourceFilterSafeBrowsingActivationThrottleTestWithCancelling,
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc b/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
index 59352bc..ec3f292 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features_test_support.cc
@@ -4,7 +4,9 @@
 
 #include "components/subresource_filter/core/browser/subresource_filter_features_test_support.h"
 
+#include <memory>
 #include <ostream>
+#include <string>
 #include <utility>
 
 #include "base/json/json_writer.h"
@@ -52,50 +54,5 @@
       base::MakeRefCounted<ConfigurationList>(std::move(config)));
 }
 
-// ScopedSubresourceFilterFeatureToggle ---------------------------------------
-
-ScopedSubresourceFilterFeatureToggle::ScopedSubresourceFilterFeatureToggle() {}
-ScopedSubresourceFilterFeatureToggle::ScopedSubresourceFilterFeatureToggle(
-    base::FeatureList::OverrideState feature_state,
-    const std::string& additional_features_to_enable) {
-  ResetSubresourceFilterState(feature_state, additional_features_to_enable);
-}
-
-void ScopedSubresourceFilterFeatureToggle::ResetSubresourceFilterState(
-    base::FeatureList::OverrideState feature_state,
-    const std::string& additional_features_to_enable) {
-  std::string enabled_features;
-  std::string disabled_features;
-
-  if (feature_state == base::FeatureList::OVERRIDE_ENABLE_FEATURE) {
-    enabled_features = kSafeBrowsingSubresourceFilter.name;
-  } else if (feature_state == base::FeatureList::OVERRIDE_DISABLE_FEATURE) {
-    disabled_features = kSafeBrowsingSubresourceFilter.name;
-  }
-
-  if (!additional_features_to_enable.empty()) {
-    if (!enabled_features.empty())
-      enabled_features += ',';
-    enabled_features += additional_features_to_enable;
-  }
-
-  scoped_configuration_.ResetConfiguration();
-  scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
-  scoped_feature_list_->InitFromCommandLine(enabled_features,
-                                            disabled_features);
-}
-
-ScopedSubresourceFilterFeatureToggle::~ScopedSubresourceFilterFeatureToggle() {}
-
-std::ostream& operator<<(std::ostream& os, const Configuration& config) {
-  std::unique_ptr<base::Value> value = config.ToTracedValue()->ToBaseValue();
-  base::DictionaryValue* dict;
-  value->GetAsDictionary(&dict);
-  std::string json;
-  base::JSONWriter::WriteWithOptions(
-      *dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
-  return os << json;
-}
-
 }  // namespace testing
 }  // namespace subresource_filter
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_test_support.h b/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
index 91ad516..b8da0be 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
+++ b/components/subresource_filter/core/browser/subresource_filter_features_test_support.h
@@ -6,12 +6,9 @@
 #define COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_SUBRESOURCE_FILTER_FEATURES_TEST_SUPPORT_H_
 
 #include <iosfwd>
-#include <memory>
-#include <string>
+#include <vector>
 
-#include "base/feature_list.h"
 #include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 
 namespace subresource_filter {
@@ -31,48 +28,18 @@
       std::vector<Configuration> configs);
   ~ScopedSubresourceFilterConfigurator();
 
-  void ResetConfiguration(
-      scoped_refptr<ConfigurationList> config_list = nullptr);
   void ResetConfiguration(Configuration config);
   void ResetConfiguration(std::vector<Configuration> config);
 
  private:
+  void ResetConfiguration(
+      scoped_refptr<ConfigurationList> config_list = nullptr);
+
   scoped_refptr<ConfigurationList> original_config_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedSubresourceFilterConfigurator);
 };
 
-// Helper class to override the state of the |kSafeBrowsingSubresourceFilter|
-// feature.
-//
-// Clears the active subresource filtering configuration override upon
-// construction, if any, and restores it on destruction. So while the instance
-// is in scope, calls to GetEnabledConfigurations() will default to returning
-// the hard-coded configuration corresponding to the forced feature state. Tests
-// that need to toggle both the feature and override the active configuration
-// should therefore do so in that order.
-class ScopedSubresourceFilterFeatureToggle {
- public:
-  ScopedSubresourceFilterFeatureToggle();
-  explicit ScopedSubresourceFilterFeatureToggle(
-      base::FeatureList::OverrideState feature_state,
-      const std::string& additional_features_to_enable = std::string());
-  ~ScopedSubresourceFilterFeatureToggle();
-
-  void ResetSubresourceFilterState(
-      base::FeatureList::OverrideState feature_state,
-      const std::string& additional_features_to_enable = std::string());
-
- private:
-  ScopedSubresourceFilterConfigurator scoped_configuration_;
-  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedSubresourceFilterFeatureToggle);
-};
-
-// For logging in tests.
-std::ostream& operator<<(std::ostream& os, const Configuration& config);
-
 }  // namespace testing
 }  // namespace subresource_filter
 
diff --git a/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc b/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
index 2e119a0f..e86b964 100644
--- a/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
+++ b/components/subresource_filter/core/browser/subresource_filter_features_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features_test_support.h"
 #include "components/subresource_filter/core/common/common_features.h"
diff --git a/components/viz/test/fake_output_surface.cc b/components/viz/test/fake_output_surface.cc
index 304b87c..521ac500 100644
--- a/components/viz/test/fake_output_surface.cc
+++ b/components/viz/test/fake_output_surface.cc
@@ -57,7 +57,8 @@
 void FakeOutputSurface::SwapBuffersAck(bool need_presentation_feedback) {
   client_->DidReceiveSwapBuffersAck();
   if (need_presentation_feedback)
-    client_->DidReceivePresentationFeedback(gfx::PresentationFeedback());
+    client_->DidReceivePresentationFeedback(
+        {base::TimeTicks::Now(), base::TimeDelta(), 0});
 }
 
 void FakeOutputSurface::BindFramebuffer() {
diff --git a/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc b/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
index f25ed52..af837fc 100644
--- a/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
+++ b/content/browser/frame_host/blocked_scheme_navigation_browsertest.cc
@@ -450,12 +450,12 @@
     DownloadManager* download_manager = BrowserContext::GetDownloadManager(
         shell()->web_contents()->GetBrowserContext());
 
-    EXPECT_TRUE(ExecuteScript(rfh, javascript));
-    Shell* new_shell = new_shell_observer.GetShell();
-
     DownloadTestObserverTerminal download_observer(
         download_manager, 1, DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
 
+    EXPECT_TRUE(ExecuteScript(rfh, javascript));
+    Shell* new_shell = new_shell_observer.GetShell();
+
     WaitForLoadStop(new_shell->web_contents());
     // If no download happens, this will timeout.
     download_observer.WaitForFinished();
diff --git a/content/browser/network_service_instance.cc b/content/browser/network_service_instance.cc
index c62cf640..374ac6da 100644
--- a/content/browser/network_service_instance.cc
+++ b/content/browser/network_service_instance.cc
@@ -58,6 +58,9 @@
     delete g_client;  // In case we're recreating the network service.
     g_client = new NetworkServiceClient(mojo::MakeRequest(&client_ptr));
     (*g_network_service_ptr)->SetClient(std::move(client_ptr));
+
+    GetContentClient()->browser()->OnNetworkServiceCreated(
+        g_network_service_ptr->get());
   }
   return g_network_service_ptr->get();
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index 656807e..19ab1f76 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -23,6 +23,7 @@
 #include "content/common/content_export.h"
 #include "ipc/ipc_sender.h"
 #include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
+#include "ui/accelerated_widget_mac/ca_transaction_observer.h"
 #include "ui/accelerated_widget_mac/display_link_mac.h"
 #include "ui/base/cocoa/remote_layer_api.h"
 #include "ui/events/gesture_detection/filtered_gesture_provider.h"
@@ -68,6 +69,7 @@
       public RenderWidgetHostNSViewClient,
       public BrowserCompositorMacClient,
       public TextInputManager::Observer,
+      public ui::CATransactionCoordinator::PreCommitObserver,
       public ui::GestureProviderClient,
       public ui::AcceleratedWidgetMacNSView,
       public IPC::Sender {
@@ -220,6 +222,10 @@
   void OnTextSelectionChanged(TextInputManager* text_input_manager,
                               RenderWidgetHostViewBase* updated_view) override;
 
+  // ui::CATransactionCoordinator::PreCommitObserver implementation
+  bool ShouldWaitInPreCommit() override;
+  base::TimeDelta PreCommitTimeout() override;
+
   // ui::GestureProviderClient implementation.
   void OnGestureEvent(const ui::GestureEventData& gesture) override;
 
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 8cd2f9c..b3cf128a 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -59,6 +59,10 @@
 using blink::WebGestureEvent;
 using blink::WebTouchEvent;
 
+namespace {
+constexpr auto kContentPaintTimeout = base::TimeDelta::FromMilliseconds(167);
+}  // namespace
+
 namespace content {
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -193,9 +197,11 @@
   // startup raciness and decrease latency.
   needs_begin_frames_ = needs_begin_frames;
   UpdateNeedsBeginFramesInternal();
+  ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
 }
 
 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() {
+  ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this);
 }
 
 void RenderWidgetHostViewMac::SetParentUiLayer(ui::Layer* parent_ui_layer) {
@@ -325,6 +331,7 @@
       LOG(ERROR) << "Failed to create display link.";
     }
   }
+  ui::CATransactionCoordinator::Get().Synchronize();
 
   // During auto-resize it is the responsibility of the caller to ensure that
   // the NSView and RenderWidgetHostImpl are kept in sync.
@@ -550,6 +557,14 @@
                                     selection->range());
 }
 
+bool RenderWidgetHostViewMac::ShouldWaitInPreCommit() {
+  return ShouldContinueToPauseForFrame();
+}
+
+base::TimeDelta RenderWidgetHostViewMac::PreCommitTimeout() {
+  return kContentPaintTimeout;
+}
+
 void RenderWidgetHostViewMac::OnGestureEvent(
     const ui::GestureEventData& gesture) {
   blink::WebGestureEvent web_gesture =
@@ -1283,6 +1298,8 @@
 }
 
 void RenderWidgetHostViewMac::PauseForPendingResizeOrRepaintsAndDraw() {
+  return;  // TODO(https://crbug.com/682825): Delete this during cleanup.
+
   if (!host() || !browser_compositor_ || host()->is_hidden()) {
     return;
   }
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index ea99d60..e686f55e 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -11651,82 +11651,6 @@
   EXPECT_LE(compositing_rect.y(), expected_offset + 2);
 }
 
-// This test verifies that if a plugin element contains a remote frame, a change
-// in attributes leading to lazy reattaching the element will detach the OOPIF
-// unconditionally.
-IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
-                       DetachRemoteContentFrameUnconditionallyInPlugins) {
-  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  GURL embed_url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
-  GURL embed_url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
-  GURL pdf_url(
-      embedded_test_server()->GetURL("c.com", "/find_in_pdf_page.pdf"));
-  const char kAttachEmbed[] =
-      "document.body.appendChild(document.createElement('embed'));"
-      "document.querySelector('embed').addEventListener('load', (e) => {"
-      "  window.domAutomationController.send(this.last_src);"
-      "});";
-  const char kUpdateEmbedAttributes[] =
-      "el = document.querySelector('embed');"
-      "el.type = '%s';"
-      "el.src = '%s';"
-      "window.last_src = el.src;";
-  const char kTextHTMLType[] = "text/html";
-  const char kPDFType[] = "application/x-google-chrome-pdf";
-  const char kInjectBeforeUnloadHandler[] =
-      "window.addEventListener('beforeunload', (e) => {"
-      "  e.returnValue = 'message';"
-      "  return e.returnValue;"
-      "});";
-
-  ASSERT_TRUE(NavigateToURL(shell(), main_url));
-  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
-  ASSERT_TRUE(ExecuteScript(root, kAttachEmbed));
-
-  // Navigate <embed> to cross-origin.
-  std::string response;
-  ASSERT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      base::StringPrintf(kUpdateEmbedAttributes, kTextHTMLType,
-                         embed_url_b.spec().c_str()),
-      &response));
-  EXPECT_EQ(embed_url_b.spec(), response);
-  ASSERT_TRUE(root->child_at(0));
-  // Add 'beforeunload' handler.
-  ASSERT_TRUE(ExecuteScript(root->child_at(0), kInjectBeforeUnloadHandler));
-  // For completeness (we do not expect the 'beforeunload' handler to even get
-  // called since detaching the frame is unconditional).
-  PrepContentsForBeforeUnloadTest(shell()->web_contents());
-
-  // Now change the embed |src| and |type| to PDF and verify that the current
-  // frame is destroyed.
-  FrameDeletedObserver delete_observer1(
-      root->child_at(0)->current_frame_host());
-  ASSERT_TRUE(
-      ExecuteScript(root, base::StringPrintf(kUpdateEmbedAttributes, kPDFType,
-                                             pdf_url.spec().c_str())));
-  delete_observer1.Wait();
-
-  // Navigate the <embed> back to |embed_url_b| and then try again with a
-  // cross-origin navigation.
-  response.clear();
-  ASSERT_TRUE(ExecuteScriptAndExtractString(
-      root,
-      base::StringPrintf(kUpdateEmbedAttributes, kTextHTMLType,
-                         embed_url_b.spec().c_str()),
-      &response));
-  EXPECT_EQ(embed_url_b.spec(), response);
-  ASSERT_TRUE(root->child_at(0));
-  ASSERT_TRUE(ExecuteScript(root->child_at(0), kInjectBeforeUnloadHandler));
-  PrepContentsForBeforeUnloadTest(shell()->web_contents());
-  FrameDeletedObserver delete_observer2(
-      root->child_at(0)->current_frame_host());
-  ASSERT_TRUE(ExecuteScript(
-      root, base::StringPrintf(kUpdateEmbedAttributes, kTextHTMLType,
-                               embed_url_c.spec().c_str())));
-  delete_observer2.Wait();
-}
-
 // Similar to ScaledIFrameRasterSize but with nested OOPIFs to ensure
 // propagation works correctly.
 #if defined(OS_ANDROID)
diff --git a/content/browser/tracing/background_tracing_manager_browsertest.cc b/content/browser/tracing/background_tracing_manager_browsertest.cc
index 374da5b..d419bac 100644
--- a/content/browser/tracing/background_tracing_manager_browsertest.cc
+++ b/content/browser/tracing/background_tracing_manager_browsertest.cc
@@ -144,6 +144,10 @@
     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, std::move(callback_));
   }
 
+  void SetUploadCallback(base::OnceClosure callback) {
+    callback_ = std::move(callback);
+  }
+
   bool TraceHasMatchingString(const char* str) {
     return last_file_contents_.find(str) != std::string::npos;
   }
@@ -151,8 +155,9 @@
   int get_receive_count() const { return receive_count_; }
 
   BackgroundTracingManager::ReceiveCallback get_receive_callback() {
-    return base::BindOnce(&BackgroundTracingManagerUploadConfigWrapper::Upload,
-                          base::Unretained(this));
+    return base::BindRepeating(
+        &BackgroundTracingManagerUploadConfigWrapper::Upload,
+        base::Unretained(this));
   }
 
  private:
@@ -1335,6 +1340,57 @@
 
 #if defined(OS_ANDROID)
 // Flaky on android: https://crbug.com/639706
+#define MAYBE_ReactiveSecondUpload DISABLED_ReactiveSecondUpload
+#else
+#define MAYBE_ReactiveSecondUpload ReactiveSecondUpload
+#endif
+
+// This tests that reactive mode uploads on a second set of triggers.
+IN_PROC_BROWSER_TEST_F(BackgroundTracingManagerBrowserTest,
+                       MAYBE_ReactiveSecondUpload) {
+  {
+    SetupBackgroundTracingManager();
+
+    base::RunLoop run_loop;
+    BackgroundTracingManagerUploadConfigWrapper upload_config_wrapper(
+        run_loop.QuitClosure());
+
+    std::unique_ptr<BackgroundTracingConfig> config = CreateReactiveConfig();
+
+    BackgroundTracingManager::TriggerHandle handle =
+        BackgroundTracingManager::GetInstance()->RegisterTriggerType(
+            "reactive_test");
+
+    EXPECT_TRUE(BackgroundTracingManager::GetInstance()->SetActiveScenario(
+        std::move(config), upload_config_wrapper.get_receive_callback(),
+        BackgroundTracingManager::NO_DATA_FILTERING));
+
+    BackgroundTracingManager::GetInstance()->TriggerNamedEvent(
+        handle, base::Bind(&StartedFinalizingCallback, base::Closure(), true));
+    // second trigger to terminate.
+    BackgroundTracingManager::GetInstance()->TriggerNamedEvent(
+        handle, base::Bind(&StartedFinalizingCallback, base::Closure(), true));
+
+    run_loop.Run();
+
+    base::RunLoop second_upload_run_loop;
+    upload_config_wrapper.SetUploadCallback(
+        second_upload_run_loop.QuitClosure());
+
+    BackgroundTracingManager::GetInstance()->TriggerNamedEvent(
+        handle, base::Bind(&StartedFinalizingCallback, base::Closure(), true));
+    // second trigger to terminate.
+    BackgroundTracingManager::GetInstance()->TriggerNamedEvent(
+        handle, base::Bind(&StartedFinalizingCallback, base::Closure(), true));
+
+    second_upload_run_loop.Run();
+
+    EXPECT_TRUE(upload_config_wrapper.get_receive_count() == 2);
+  }
+}
+
+#if defined(OS_ANDROID)
+// Flaky on android: https://crbug.com/639706
 #define MAYBE_ReactiveSecondTriggerMustMatchForTermination \
         DISABLED_ReactiveSecondTriggerMustMatchForTermination
 #else
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index c6aefd6..30d131e 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -507,11 +507,12 @@
                           file_contents->size() / 1024);
 
   if (!receive_callback_.is_null()) {
-    std::move(receive_callback_)
-        .Run(file_contents, std::move(metadata),
-             base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete,
-                            base::Unretained(this)));
+    receive_callback_.Run(
+        file_contents, std::move(metadata),
+        base::BindOnce(&BackgroundTracingManagerImpl::OnFinalizeComplete,
+                       base::Unretained(this)));
   }
+
   if (!started_finalizing_closure.is_null())
     std::move(started_finalizing_closure).Run();
 }
diff --git a/content/public/browser/background_tracing_manager.h b/content/public/browser/background_tracing_manager.h
index dca9d8a3..b4cd53f 100644
--- a/content/public/browser/background_tracing_manager.h
+++ b/content/public/browser/background_tracing_manager.h
@@ -41,9 +41,9 @@
   //
   using FinishedProcessingCallback = base::OnceCallback<void(bool success)>;
   using ReceiveCallback =
-      base::OnceCallback<void(const scoped_refptr<base::RefCountedString>&,
-                              std::unique_ptr<const base::DictionaryValue>,
-                              FinishedProcessingCallback)>;
+      base::RepeatingCallback<void(const scoped_refptr<base::RefCountedString>&,
+                                   std::unique_ptr<const base::DictionaryValue>,
+                                   FinishedProcessingCallback)>;
 
   // Set the triggering rules for when to start recording.
   //
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index e7d6bcc..0bbc532 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -30,6 +30,7 @@
 #include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/network_service.mojom.h"
 #include "services/service_manager/sandbox/sandbox_type.h"
 #include "storage/browser/quota/quota_manager.h"
 #include "ui/gfx/image/image_skia.h"
@@ -625,6 +626,16 @@
     RenderFrameHost* frame,
     network::mojom::WebSocketRequest* request) {}
 
+std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>
+ContentBrowserClient::WillCreateURLLoaderRequestInterceptors(
+    NavigationUIData* navigation_ui_data,
+    int frame_tree_node_id) {
+  return std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>();
+}
+
+void ContentBrowserClient::OnNetworkServiceCreated(
+    network::mojom::NetworkService* network_service) {}
+
 network::mojom::NetworkContextPtr ContentBrowserClient::CreateNetworkContext(
     BrowserContext* context,
     bool in_memory,
@@ -739,11 +750,4 @@
   return nullptr;
 }
 
-std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>
-ContentBrowserClient::WillCreateURLLoaderRequestInterceptors(
-    NavigationUIData* navigation_ui_data,
-    int frame_tree_node_id) {
-  return std::vector<std::unique_ptr<URLLoaderRequestInterceptor>>();
-}
-
 }  // namespace content
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index b7efb73..efadc76c 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -33,7 +33,7 @@
 #include "media/mojo/interfaces/remoting.mojom.h"
 #include "net/base/mime_util.h"
 #include "net/cookies/canonical_cookie.h"
-#include "services/network/public/mojom/network_service.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/websocket.mojom.h"
 #include "services/service_manager/embedder/embedded_service_info.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
@@ -106,7 +106,7 @@
 
 namespace network {
 namespace mojom {
-class NetworkContext;
+class NetworkService;
 }
 struct ResourceRequest;
 }  // namespace network
@@ -1057,6 +1057,13 @@
   WillCreateURLLoaderRequestInterceptors(NavigationUIData* navigation_ui_data,
                                          int frame_tree_node_id);
 
+  // Called when the NetworkService, accessible through
+  // content::GetNetworkService(), is created. Implementations should avoid
+  // calling into GetNetworkService() again to avoid re-entrancy if the service
+  // fails to start.
+  virtual void OnNetworkServiceCreated(
+      network::mojom::NetworkService* network_service);
+
   // Creates a NetworkContext for a BrowserContext's StoragePartition. If the
   // network service is enabled, it must return a NetworkContext using the
   // network service. If the network service is disabled, the embedder may
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index fd52438..717afc3f 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -10,7 +10,6 @@
 #include <tuple>
 #include <utility>
 
-#include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
@@ -18,7 +17,6 @@
 #include "base/json/json_reader.h"
 #include "base/macros.h"
 #include "base/process/kill.h"
-#include "base/rand_util.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/pattern.h"
@@ -134,21 +132,24 @@
 class InterstitialObserver : public content::WebContentsObserver {
  public:
   InterstitialObserver(content::WebContents* web_contents,
-                       const base::Closure& attach_callback,
-                       const base::Closure& detach_callback)
+                       base::OnceClosure attach_callback,
+                       base::OnceClosure detach_callback)
       : WebContentsObserver(web_contents),
-        attach_callback_(attach_callback),
-        detach_callback_(detach_callback) {
-  }
+        attach_callback_(std::move(attach_callback)),
+        detach_callback_(std::move(detach_callback)) {}
   ~InterstitialObserver() override {}
 
   // WebContentsObserver methods:
-  void DidAttachInterstitialPage() override { attach_callback_.Run(); }
-  void DidDetachInterstitialPage() override { detach_callback_.Run(); }
+  void DidAttachInterstitialPage() override {
+    std::move(attach_callback_).Run();
+  }
+  void DidDetachInterstitialPage() override {
+    std::move(detach_callback_).Run();
+  }
 
  private:
-  base::Closure attach_callback_;
-  base::Closure detach_callback_;
+  base::OnceClosure attach_callback_;
+  base::OnceClosure detach_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(InterstitialObserver);
 };
@@ -1302,16 +1303,16 @@
 }
 
 void FetchHistogramsFromChildProcesses() {
-  scoped_refptr<content::MessageLoopRunner> runner = new MessageLoopRunner;
+  base::RunLoop run_loop;
 
   FetchHistogramsAsynchronously(
-      base::ThreadTaskRunnerHandle::Get(), runner->QuitClosure(),
+      base::ThreadTaskRunnerHandle::Get(), run_loop.QuitClosure(),
       // If this call times out, it means that a child process is not
       // responding, which is something we should not ignore.  The timeout is
       // set to be longer than the normal browser test timeout so that it will
       // be prempted by the normal timeout.
       TestTimeouts::action_max_timeout());
-  runner->Run();
+  run_loop.Run();
 }
 
 void SetupCrossSiteRedirector(net::EmbeddedTestServer* embedded_test_server) {
@@ -1322,12 +1323,10 @@
 void WaitForInterstitialAttach(content::WebContents* web_contents) {
   if (web_contents->ShowingInterstitialPage())
     return;
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
-  InterstitialObserver observer(web_contents,
-                                loop_runner->QuitClosure(),
-                                base::Closure());
-  loop_runner->Run();
+  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  InterstitialObserver observer(web_contents, run_loop.QuitClosure(),
+                                base::OnceClosure());
+  run_loop.Run();
 }
 
 void WaitForInterstitialDetach(content::WebContents* web_contents) {
@@ -1338,15 +1337,13 @@
                                          const base::Closure& task) {
   if (!web_contents || !web_contents->ShowingInterstitialPage())
     return;
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
-  InterstitialObserver observer(web_contents,
-                                base::Closure(),
-                                loop_runner->QuitClosure());
+  base::RunLoop run_loop;
+  InterstitialObserver observer(web_contents, base::OnceClosure(),
+                                run_loop.QuitClosure());
   if (!task.is_null())
     task.Run();
   // At this point, web_contents may have been deleted.
-  loop_runner->Run();
+  run_loop.Run();
 }
 
 bool WaitForRenderFrameReady(RenderFrameHost* rfh) {
@@ -1378,11 +1375,10 @@
 }
 
 void WaitForAccessibilityFocusChange() {
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
+  base::RunLoop run_loop;
   BrowserAccessibilityManager::SetFocusChangeCallbackForTesting(
-      loop_runner->QuitClosure());
-  loop_runner->Run();
+      run_loop.QuitClosure());
+  run_loop.Run();
 }
 
 ui::AXNodeData GetFocusedAccessibilityNodeInfo(WebContents* web_contents) {
@@ -1736,22 +1732,19 @@
 }
 
 RenderProcessHostWatcher::RenderProcessHostWatcher(
-    RenderProcessHost* render_process_host, WatchType type)
+    RenderProcessHost* render_process_host,
+    WatchType type)
     : render_process_host_(render_process_host),
       type_(type),
       did_exit_normally_(true),
-      message_loop_runner_(new MessageLoopRunner) {
+      quit_closure_(run_loop_.QuitClosure()) {
   render_process_host_->AddObserver(this);
 }
 
 RenderProcessHostWatcher::RenderProcessHostWatcher(WebContents* web_contents,
                                                    WatchType type)
-    : render_process_host_(web_contents->GetMainFrame()->GetProcess()),
-      type_(type),
-      did_exit_normally_(true),
-      message_loop_runner_(new MessageLoopRunner) {
-  render_process_host_->AddObserver(this);
-}
+    : RenderProcessHostWatcher(web_contents->GetMainFrame()->GetProcess(),
+                               type) {}
 
 RenderProcessHostWatcher::~RenderProcessHostWatcher() {
   if (render_process_host_)
@@ -1759,7 +1752,7 @@
 }
 
 void RenderProcessHostWatcher::Wait() {
-  message_loop_runner_->Run();
+  run_loop_.Run();
 }
 
 void RenderProcessHostWatcher::RenderProcessExited(
@@ -1768,14 +1761,14 @@
   did_exit_normally_ =
       info.status == base::TERMINATION_STATUS_NORMAL_TERMINATION;
   if (type_ == WATCH_FOR_PROCESS_EXIT)
-    message_loop_runner_->Quit();
+    std::move(quit_closure_).Run();
 }
 
 void RenderProcessHostWatcher::RenderProcessHostDestroyed(
     RenderProcessHost* host) {
   render_process_host_ = nullptr;
   if (type_ == WATCH_FOR_HOST_DESTRUCTION)
-    message_loop_runner_->Quit();
+    std::move(quit_closure_).Run();
 }
 
 DOMMessageQueue::DOMMessageQueue() {
@@ -1796,8 +1789,8 @@
                               const NotificationDetails& details) {
   Details<std::string> dom_op_result(details);
   message_queue_.push(*dom_op_result.ptr());
-  if (message_loop_runner_)
-    message_loop_runner_->Quit();
+  if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 void DOMMessageQueue::RenderProcessGone(base::TerminationStatus status) {
@@ -1808,8 +1801,8 @@
       break;
     default:
       renderer_crashed_ = true;
-      if (message_loop_runner_.get())
-        message_loop_runner_->Quit();
+      if (quit_closure_)
+        std::move(quit_closure_).Run();
       break;
   }
 }
@@ -1822,9 +1815,9 @@
   DCHECK(message);
   if (!renderer_crashed_ && message_queue_.empty()) {
     // This will be quit when a new message comes in.
-    message_loop_runner_ =
-        new MessageLoopRunner(MessageLoopRunner::QuitMode::IMMEDIATE);
-    message_loop_runner_->Run();
+    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+    quit_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
   }
   return PopMessage(message);
 }
@@ -1879,16 +1872,17 @@
   web_contents_ = web_contents;
   child_observer_.reset(new RenderViewCreatedObserver(web_contents));
 
-  if (runner_.get())
-    runner_->QuitClosure().Run();
+  if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 WebContents* WebContentsAddedObserver::GetWebContents() {
   if (web_contents_)
     return web_contents_;
 
-  runner_ = new MessageLoopRunner();
-  runner_->Run();
+  base::RunLoop run_loop;
+  quit_closure_ = run_loop.QuitClosure();
+  run_loop.Run();
   return web_contents_;
 }
 
@@ -1973,14 +1967,14 @@
 }
 
 void RenderFrameSubmissionObserver::Quit() {
-  if (run_loop_)
-    run_loop_->Quit();
+  if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 void RenderFrameSubmissionObserver::Wait() {
-  run_loop_ = std::make_unique<base::RunLoop>();
-  run_loop_->Run();
-  run_loop_.reset();
+  base::RunLoop run_loop;
+  quit_closure_ = run_loop.QuitClosure();
+  run_loop.Run();
 }
 
 void RenderFrameSubmissionObserver::OnRenderFrameMetadataChanged() {
@@ -2013,14 +2007,14 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   render_widget_host_->Send(new ViewMsg_WaitForNextFrameForTests(
       render_widget_host_->GetRoutingID(), routing_id_));
-  run_loop_.reset(new base::RunLoop());
-  run_loop_->Run();
-  run_loop_.reset(nullptr);
+  base::RunLoop run_loop;
+  quit_closure_ = run_loop.QuitClosure();
+  run_loop.Run();
 }
 
 void MainThreadFrameObserver::Quit() {
-  if (run_loop_)
-    run_loop_->Quit();
+  if (quit_closure_)
+    std::move(quit_closure_).Run();
 }
 
 bool MainThreadFrameObserver::OnMessageReceived(const IPC::Message& msg) {
@@ -2052,8 +2046,8 @@
   if (event.GetType() == wait_for_type_) {
     ack_result_ = ack_state;
     ack_source_ = ack_source;
-    if (!quit_.is_null())
-      quit_.Run();
+    if (quit_closure_)
+      std::move(quit_closure_).Run();
   }
 }
 
@@ -2064,7 +2058,7 @@
 InputEventAckState InputMsgWatcher::WaitForAck() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   base::RunLoop run_loop;
-  base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure());
+  quit_closure_ = run_loop.QuitClosure();
   run_loop.Run();
   return ack_result_;
 }
@@ -2106,14 +2100,14 @@
 void InputEventAckWaiter::Wait() {
   if (!event_received_) {
     base::RunLoop run_loop;
-    quit_ = run_loop.QuitClosure();
+    quit_closure_ = run_loop.QuitClosure();
     run_loop.Run();
   }
 }
 
 void InputEventAckWaiter::Reset() {
   event_received_ = false;
-  quit_ = base::Closure();
+  quit_closure_ = base::OnceClosure();
 }
 
 void InputEventAckWaiter::OnInputEventAck(InputEventAckSource source,
@@ -2121,8 +2115,8 @@
                                           const blink::WebInputEvent& event) {
   if (predicate_.Run(source, state, event)) {
     event_received_ = true;
-    if (quit_)
-      quit_.Run();
+    if (quit_closure_)
+      std::move(quit_closure_).Run();
   }
 }
 
@@ -2156,22 +2150,21 @@
 class FrameFocusedObserver::FrameTreeNodeObserverImpl
     : public FrameTreeNode::Observer {
  public:
-  explicit FrameTreeNodeObserverImpl(FrameTreeNode* owner)
-      : owner_(owner), message_loop_runner_(new MessageLoopRunner) {
+  explicit FrameTreeNodeObserverImpl(FrameTreeNode* owner) : owner_(owner) {
     owner->AddObserver(this);
   }
   ~FrameTreeNodeObserverImpl() override { owner_->RemoveObserver(this); }
 
-  void Run() { message_loop_runner_->Run(); }
+  void Run() { run_loop_.Run(); }
 
   void OnFrameTreeNodeFocused(FrameTreeNode* node) override {
     if (node == owner_)
-      message_loop_runner_->Quit();
+      run_loop_.Quit();
   }
 
  private:
   FrameTreeNode* owner_;
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  base::RunLoop run_loop_;
 };
 
 FrameFocusedObserver::FrameFocusedObserver(RenderFrameHost* owner_host)
@@ -2311,10 +2304,10 @@
 
   // Wait for the desired state if needed.
   if (current_state_ < desired_state_) {
-    DCHECK(!loop_runner_);
-    loop_runner_ = new MessageLoopRunner();
-    loop_runner_->Run();
-    loop_runner_ = nullptr;
+    DCHECK(!quit_closure_);
+    base::RunLoop run_loop;
+    quit_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
   }
 
   // Return false if the navigation did not reach the state specified by the
@@ -2326,8 +2319,8 @@
   // If the state the user was waiting for has been reached, exit the message
   // loop.
   if (current_state_ >= desired_state_) {
-    if (loop_runner_)
-      loop_runner_->Quit();
+    if (quit_closure_)
+      std::move(quit_closure_).Run();
     return;
   }
 
@@ -2364,14 +2357,12 @@
 
 ConsoleObserverDelegate::ConsoleObserverDelegate(WebContents* web_contents,
                                                  const std::string& filter)
-    : web_contents_(web_contents),
-      filter_(filter),
-      message_loop_runner_(new MessageLoopRunner) {}
+    : web_contents_(web_contents), filter_(filter) {}
 
 ConsoleObserverDelegate::~ConsoleObserverDelegate() {}
 
 void ConsoleObserverDelegate::Wait() {
-  message_loop_runner_->Run();
+  run_loop_.Run();
 }
 
 bool ConsoleObserverDelegate::DidAddMessageToConsole(
@@ -2385,7 +2376,7 @@
   std::string ascii_message = base::UTF16ToASCII(message);
   if (base::MatchPattern(ascii_message, filter_)) {
     message_ = ascii_message;
-    message_loop_runner_->Quit();
+    run_loop_.Quit();
   }
   return false;
 }
@@ -2450,9 +2441,7 @@
 class MockOverscrollControllerImpl : public OverscrollController,
                                      public MockOverscrollController {
  public:
-  MockOverscrollControllerImpl()
-      : content_scrolling_(false),
-        message_loop_runner_(new MessageLoopRunner) {}
+  MockOverscrollControllerImpl() : content_scrolling_(false) {}
   ~MockOverscrollControllerImpl() override {}
 
   // OverscrollController:
@@ -2466,20 +2455,23 @@
     if (event.GetType() == blink::WebInputEvent::kGestureScrollUpdate &&
         processed) {
       content_scrolling_ = true;
-      if (message_loop_runner_->loop_running())
-        message_loop_runner_->Quit();
+      if (quit_closure_)
+        std::move(quit_closure_).Run();
     }
   }
 
   // MockOverscrollController:
   void WaitForConsumedScroll() override {
-    if (!content_scrolling_)
-      message_loop_runner_->Run();
+    if (!content_scrolling_) {
+      base::RunLoop run_loop;
+      quit_closure_ = run_loop.QuitClosure();
+      run_loop.Run();
+    }
   }
 
  private:
   bool content_scrolling_;
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  base::OnceClosure quit_closure_;
 
   DISALLOW_COPY_AND_ASSIGN(MockOverscrollControllerImpl);
 };
@@ -2503,8 +2495,8 @@
 
 ContextMenuFilter::ContextMenuFilter()
     : content::BrowserMessageFilter(FrameMsgStart),
-      message_loop_runner_(new content::MessageLoopRunner),
-      handled_(false) {}
+      run_loop_(new base::RunLoop),
+      quit_closure_(run_loop_->QuitClosure()) {}
 
 bool ContextMenuFilter::OnMessageReceived(const IPC::Message& message) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -2520,8 +2512,9 @@
 }
 
 void ContextMenuFilter::Wait() {
-  if (!handled_)
-    message_loop_runner_->Run();
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  run_loop_->Run();
+  run_loop_ = nullptr;
 }
 
 ContextMenuFilter::~ContextMenuFilter() {}
@@ -2529,9 +2522,8 @@
 void ContextMenuFilter::OnContextMenu(
     const content::ContextMenuParams& params) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  handled_ = true;
   last_params_ = params;
-  message_loop_runner_->Quit();
+  std::move(quit_closure_).Run();
 }
 
 WebContents* GetEmbedderForGuest(content::WebContents* guest) {
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 52fb5b6..09cbf2be 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -76,7 +76,6 @@
 struct FrameVisualProperties;
 class FrameTreeNode;
 class InterstitialPage;
-class MessageLoopRunner;
 class NavigationHandle;
 class RenderViewHost;
 class RenderWidgetHost;
@@ -612,7 +611,8 @@
   WatchType type_;
   bool did_exit_normally_;
 
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  base::RunLoop run_loop_;
+  base::OnceClosure quit_closure_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderProcessHostWatcher);
 };
@@ -655,7 +655,7 @@
  private:
   NotificationRegistrar registrar_;
   base::queue<std::string> message_queue_;
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  base::OnceClosure quit_closure_;
   bool renderer_crashed_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(DOMMessageQueue);
@@ -685,7 +685,7 @@
 
   WebContents* web_contents_;
   std::unique_ptr<RenderViewCreatedObserver> child_observer_;
-  scoped_refptr<MessageLoopRunner> runner_;
+  base::OnceClosure quit_closure_;
 
   DISALLOW_COPY_AND_ASSIGN(WebContentsAddedObserver);
 };
@@ -772,7 +772,7 @@
   bool break_on_any_frame_ = false;
 
   RenderFrameMetadataProvider* render_frame_metadata_provider_ = nullptr;
-  std::unique_ptr<base::RunLoop> run_loop_;
+  base::OnceClosure quit_closure_;
   int render_frame_count_ = 0;
 };
 
@@ -804,7 +804,7 @@
   void Quit();
 
   RenderWidgetHost* render_widget_host_;
-  std::unique_ptr<base::RunLoop> run_loop_;
+  base::OnceClosure quit_closure_;
   int routing_id_;
 
   DISALLOW_COPY_AND_ASSIGN(MainThreadFrameObserver);
@@ -839,7 +839,7 @@
   blink::WebInputEvent::Type wait_for_type_;
   InputEventAckState ack_result_;
   InputEventAckSource ack_source_;
-  base::Closure quit_;
+  base::OnceClosure quit_closure_;
 
   DISALLOW_COPY_AND_ASSIGN(InputMsgWatcher);
 };
@@ -874,7 +874,7 @@
   RenderWidgetHost* render_widget_host_;
   InputEventAckPredicate predicate_;
   bool event_received_;
-  base::Closure quit_;
+  base::OnceClosure quit_closure_;
 
   DISALLOW_COPY_AND_ASSIGN(InputEventAckWaiter);
 };
@@ -1016,8 +1016,8 @@
   bool navigation_paused_;
   NavigationState current_state_;
   NavigationState desired_state_;
-  scoped_refptr<MessageLoopRunner> loop_runner_;
   bool was_successful_ = false;
+  base::OnceClosure quit_closure_;
 
   base::WeakPtrFactory<TestNavigationManager> weak_factory_;
 
@@ -1067,8 +1067,7 @@
   std::string filter_;
   std::string message_;
 
-  // The MessageLoopRunner used to spin the message loop.
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
+  base::RunLoop run_loop_;
 
   DISALLOW_COPY_AND_ASSIGN(ConsoleObserverDelegate);
 };
@@ -1145,9 +1144,9 @@
 
   void OnContextMenu(const content::ContextMenuParams& params);
 
-  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
+  std::unique_ptr<base::RunLoop> run_loop_;
+  base::OnceClosure quit_closure_;
   content::ContextMenuParams last_params_;
-  bool handled_;
 
   DISALLOW_COPY_AND_ASSIGN(ContextMenuFilter);
 };
diff --git a/content/test/gpu/generate_buildbot_json.py b/content/test/gpu/generate_buildbot_json.py
deleted file mode 100755
index 9284f750..0000000
--- a/content/test/gpu/generate_buildbot_json.py
+++ /dev/null
@@ -1,3087 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Script to generate chromium.gpu.json and chromium.gpu.fyi.json in
-the src/testing/buildbot directory. Maintaining these files by hand is
-too unwieldy.
-"""
-
-import copy
-import json
-import os
-import string
-import sys
-
-THIS_DIR = os.path.dirname(os.path.abspath(__file__))
-SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(THIS_DIR)))
-
-# Current stable Windows NVIDIA Quadro P400 device/driver/os identifiers.
-WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER = '10de:1cb3-23.21.13.8792'
-WIN7_NVIDIA_QUADRO_P400_STABLE_OS = 'Windows-2008ServerR2-SP1'
-WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER = '10de:1cb3-23.21.13.8816'
-WIN10_NVIDIA_QUADRO_P400_STABLE_OS = 'Windows-10'
-
-# Current experimental Windows NVIDIA Quadro P400 device/driver/os
-# identifiers.
-WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_DRIVER = '10de:1cb3-23.21.13.8816'
-WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_OS = 'Windows-10'
-
-# Use this to match all drivers for the NVIDIA Quadro P400.
-NVIDIA_QUADRO_P400_ALL_DRIVERS = '10de:1cb3-*'
-
-# Linux NVIDIA Quadro P400.
-LINUX_QUADRO_P400_STABLE_DRIVER = '10de:1cb3-384.90'
-
-# Intel HD 630 (both Windows and Linux).
-INTEL_HD_630 = '8086:5912'
-WIN10_INTEL_HD_630_STABLE_DRIVER = '8086:5912-23.20.16.4877'
-WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER = '8086:5912-24.20.100.6025'
-
-# "Types" of waterfalls and bots. A bot's type is the union of its own
-# type and the type of its waterfall. Predicates can apply to these
-# sets in order to run tests only on a certain subset of the bots.
-class Types(object):
-  GPU = 'gpu'
-  GPU_FYI = 'gpu_fyi'
-  OPTIONAL = 'optional'
-  V8_FYI = 'v8_fyi'
-  # The Win ANGLE AMD tryserver is split off because there isn't
-  # enough capacity to run all the tests from chromium.gpu.fyi's Win
-  # AMD bot on a tryserver. It represents some of the tests on
-  # win_angle_rel_ng and is not a real machine on the waterfall.
-  WIN_ANGLE_AMD_TRYSERVER = 'win_angle_amd_tryserver'
-  # The dEQP tests use a different compiler configuration than the
-  # rest of the bots; they're the only target which use exceptions and
-  # RTTI. They're split off so that these specialized compiler options
-  # apply only to these targets.
-  DEQP = 'deqp'
-  # The experimental try servers are meant to test new driver versions
-  # and OS flavors.
-  EXPERIMENTAL = 'experimental'
-  # This deliberately doesn't match any predicate, and is used to
-  # completely disable a particular trybot.
-  NOOP = 'noop'
-
-# The predicate functions receive a list of types as input and
-# determine whether the test should run on the given bot.
-class Predicates(object):
-  @staticmethod
-  def DEFAULT(x):
-    # By default, tests run on the chromium.gpu and chromium.gpu.fyi
-    # waterfalls, but not on the DEQP bots, not on the optional
-    # tryservers, not on the client.v8.fyi waterfall, nor on the Win
-    # ANGLE AMD tryserver.
-    return Types.DEQP not in x and Types.OPTIONAL not in x and \
-      Types.V8_FYI not in x and Types.WIN_ANGLE_AMD_TRYSERVER not in x and \
-      Types.NOOP not in x
-
-  @staticmethod
-  def FYI_ONLY(x):
-    # This predicate is more complex than desired because the optional
-    # tryservers and the Win ANGLE AMD tryserver are considered to be
-    # on the chromium.gpu.fyi waterfall.
-    return Types.GPU_FYI in x and Types.DEQP not in x and \
-      Types.OPTIONAL not in x and Types.WIN_ANGLE_AMD_TRYSERVER not in x and \
-      Types.NOOP not in x
-
-  @staticmethod
-  def FYI_AND_OPTIONAL(x):
-    return Predicates.FYI_ONLY(x) or Types.OPTIONAL in x
-
-  @staticmethod
-  def FYI_AND_OPTIONAL_AND_WIN_ANGLE_AMD(x):
-    return Predicates.FYI_ONLY(x) or Types.OPTIONAL in x or \
-      Types.WIN_ANGLE_AMD_TRYSERVER in x
-
-  @staticmethod
-  def FYI_OPTIONAL_AND_V8(x):
-    return Predicates.FYI_AND_OPTIONAL(x) or Types.V8_FYI in x
-
-  @staticmethod
-  def FYI_OPTIONAL_V8_AND_WIN_ANGLE_AMD(x):
-    return Predicates.FYI_OPTIONAL_AND_V8(x) or \
-      Types.WIN_ANGLE_AMD_TRYSERVER in x
-
-  @staticmethod
-  def DEFAULT_PLUS_V8(x):
-    return Predicates.DEFAULT(x) or Types.V8_FYI in x
-
-  @staticmethod
-  def DEFAULT_AND_OPTIONAL(x):
-    return Predicates.DEFAULT(x) or Types.OPTIONAL in x
-
-  @staticmethod
-  def DEQP(x):
-    return Types.DEQP in x
-
-  @staticmethod
-  def EXPERIMENTAL_CONDITIONALLY(x):
-    return Types.EXPERIMENTAL in x
-
-# Most of the bots live in the Chrome-GPU pool as defined here (Google
-# employees only, sorry):
-# https://chrome-internal.googlesource.com/infradata/config/+/master/configs/
-#   chromium-swarm/bots.cfg
-#
-# Some of them, like the Mac Minis and Nexus 5X devices, are shared
-# resources and live in the regular Chrome pool.
-
-WATERFALL = {
-  'name': 'chromium.gpu',
-  'type': Types.GPU,
-
-  'builders': {
-    'GPU Win Builder' : {},
-    'GPU Win Builder (dbg)' : {},
-    'GPU Mac Builder' : {},
-    'GPU Mac Builder (dbg)' : {},
-    'GPU Linux Builder' : {},
-    'GPU Linux Builder (dbg)' : {},
-   },
-
-  'testers': {
-    'Win10 Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'use_gpu_trigger_script': True,
-    },
-    'Win10 Debug (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Mac Release (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac Debug (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac Retina Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac Retina Debug (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Linux Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux Debug (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Android Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'MMB29Q',
-          'os': 'Android'
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-  }
-}
-
-FYI_WATERFALL = {
-  'name': 'chromium.gpu.fyi',
-  'type': Types.GPU_FYI,
-
-  'builders': {
-    'GPU FYI Win Builder' : {},
-    'GPU FYI Win Builder (dbg)' : {},
-    'GPU FYI Win dEQP Builder': {},
-    'GPU FYI Win x64 Builder' : {},
-    'GPU FYI Win x64 Builder (dbg)' : {},
-    'GPU FYI Win x64 dEQP Builder' : {},
-    'GPU FYI Mac Builder' : {},
-    'GPU FYI Mac Builder (dbg)' : {},
-    'GPU FYI Mac dEQP Builder' : {},
-    'GPU FYI Linux Builder' : {},
-    'GPU FYI Linux Builder (dbg)' : {},
-    'GPU FYI Linux Ozone Builder' : {},
-    'GPU FYI Linux dEQP Builder' : {},
-  },
-
-  'testers': {
-    'Win7 FYI Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'use_gpu_trigger_script': True,
-    },
-    'Win10 FYI dEQP Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.DEQP,
-    },
-    # TODO(kbr): "Experimental" caused too-long path names pre-LUCI.
-    # crbug.com/812000
-    'Win10 FYI Exp Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.EXPERIMENTAL,
-      # This should match another config name specified in this file.
-      'stable_tester_name': 'Win10 FYI Release (NVIDIA)',
-    },
-    'Win10 FYI Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'use_gpu_trigger_script': True,
-    },
-    'Win10 FYI Debug (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win7 FYI Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6613',
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win7 FYI Debug (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6613',
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win7 FYI dEQP Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6613',
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.DEQP,
-    },
-    'Win10 FYI Exp Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER,
-          'os': 'Windows-10',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win10 FYI Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_INTEL_HD_630_STABLE_DRIVER,
-          'os': 'Windows-10',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win10 FYI dEQP Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_INTEL_HD_630_STABLE_DRIVER,
-          'os': 'Windows-10',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.DEQP,
-    },
-    'Win7 FYI x64 Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release_x64',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Win7 FYI x64 dEQP Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release_x64',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.DEQP,
-    },
-    'Mac FYI Release (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Debug (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac Pro FYI Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:679e',
-          'os': 'Mac-10.12',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      # TODO(kbr): this separate dictionary is a hack to avoid generalizing the
-      # "swarming_dimensions" handling in this generator script. When merging
-      # this script with the one in src/testing/buildbot/, this will no longer
-      # be necessary.
-      'swarming_settings': {
-        # There are only two bots of this type in the Swarming pool right now,
-        # so we have to increase the default expiration time of 1 hour (3600
-        # seconds) to prevent webgl2_conformance_tests' shards from timing out.
-        'expiration': 10800,
-      },
-      'build_config': 'Release',
-      # Even though this bot is a one-off, it's still in the Swarming pool.
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Retina Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '10de:0fe9',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Retina Debug (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '10de:0fe9',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Retina Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Retina Debug (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Experimental Release (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.13.4',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Experimental Retina Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.13.4',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI Experimental Retina Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '10de:0fe9',
-          'hidpi': '1',
-          'os': 'Mac-10.13.4',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      # TODO(kbr): this separate dictionary is a hack to avoid generalizing the
-      # "swarming_dimensions" handling in this generator script. When merging
-      # this script with the one in src/testing/buildbot/, this will no longer
-      # be necessary.
-      'swarming_settings': {
-        # There's only one bot of this type in the Swarming pool right now, so
-        # we have to increase the default expiration time of 1 hour (3600
-        # seconds) to prevent webgl2_conformance_tests' shards from timing out.
-        'expiration': 10800,
-      },
-      'build_config': 'Release',
-      # Even though this bot is a one-off, it's still in the Swarming pool.
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Mac FYI GPU ASAN Release': {
-      # This bot spawns jobs on multiple GPU types.
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'is_asan': True,
-    },
-    'Mac FYI dEQP Release AMD': {
-      # This bot spawns jobs on multiple GPU types.
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'type': Types.DEQP,
-    },
-    'Mac FYI dEQP Release Intel': {
-      # This bot spawns jobs on multiple GPU types.
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'type': Types.DEQP,
-    },
-    'Linux FYI Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux FYI Debug (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Debug',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux FYI dEQP Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-      'type': Types.DEQP,
-    },
-    'Linux FYI Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': INTEL_HD_630,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux FYI dEQP Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': INTEL_HD_630,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-      'type': Types.DEQP,
-    },
-    'Linux FYI Release (AMD R7 240)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6613',
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      # TODO(kbr): this separate dictionary is a hack to avoid generalizing the
-      # "swarming_dimensions" handling in this generator script. When merging
-      # this script with the one in src/testing/buildbot/, this will no longer
-      # be necessary.
-      'swarming_settings': {
-        # There's only one bot of this type in the Swarming pool right now, so
-        # we have to increase the default expiration time of 1 hour (3600
-        # seconds) to prevent webgl2_conformance_tests' shards from timing out.
-        'expiration': 10800,
-      },
-      'build_config': 'Release',
-      # Even though this bot is a one-off, it's still in the Swarming pool.
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux FYI GPU TSAN Release': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-      'instrumentation_type': 'tsan',
-    },
-    'Linux FYI Ozone (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:1912',
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      # Even though this bot is a one-off, it's still in the Swarming pool.
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Android FYI Release (Nexus 5)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'hammerhead',
-          'device_os': 'L',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'MMB29Q',
-          'os': 'Android'
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI Release (Nexus 6)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'shamu',
-          'device_os': 'L',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI Release (Nexus 6P)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'angler',
-          'device_os': 'M',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI Release (Nexus 9)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'flounder',
-          'device_os': 'M',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI Release (NVIDIA Shield TV)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'foster',
-          'device_os': 'N',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI dEQP Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'MMB29Q',
-          'os': 'Android'
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-      'type': Types.DEQP,
-    },
-    'Android FYI 32 Vk Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'O',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI 64 Vk Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'O',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-    'Android FYI 32 dEQP Vk Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'O',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-      'type': Types.DEQP,
-    },
-    'Android FYI 64 dEQP Vk Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'O',
-          'os': 'Android',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-      'type': Types.DEQP,
-    },
-
-    # The following "optional" testers don't actually exist on the
-    # waterfall. They are present here merely to specify additional
-    # tests which aren't on the main tryservers. Unfortunately we need
-    # a completely different (redundant) bot specification to handle
-    # this.
-
-    'Optional Win10 Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.OPTIONAL,
-      'use_gpu_trigger_script': True,
-    },
-    'Optional Win10 Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': WIN10_INTEL_HD_630_STABLE_DRIVER,
-          'os': 'Windows-10',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Mac Release (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Mac Retina Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '10de:0fe9',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Mac Retina Release (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6821',
-          'hidpi': '1',
-          'os': 'Mac-10.12.6',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Linux Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Linux Release (Intel HD 630)': {
-      'swarming_dimensions': [
-        {
-          'gpu': INTEL_HD_630,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-      'type': Types.OPTIONAL,
-    },
-    'Optional Android Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'MMB29Q',
-          'os': 'Android'
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-      'type': Types.NOOP,
-    },
-    # This tryserver doesn't actually exist; it's a separate
-    # configuration from the Win AMD bot on this waterfall because we
-    # don't have enough tryserver capacity to run all the tests from
-    # that bot on win_angle_rel_ng.
-    'Win7 ANGLE Tryserver (AMD)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '1002:6613',
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-      'type': Types.WIN_ANGLE_AMD_TRYSERVER,
-    },
-  }
-}
-
-V8_FYI_WATERFALL = {
-  'name': 'client.v8.fyi',
-  'type': Types.V8_FYI,
-
-  'prologue': {
-    "V8 Android GN (dbg)": {
-      "additional_compile_targets": [
-        "chrome_public_apk"
-      ],
-      "gtest_tests": []
-    },
-    "V8 Linux GN": {
-      "additional_compile_targets": [
-        "accessibility_unittests",
-        "aura_unittests",
-        "browser_tests",
-        "cacheinvalidation_unittests",
-        "capture_unittests",
-        "cast_unittests",
-        "cc_unittests",
-        "chromedriver_unittests",
-        "components_browsertests",
-        "components_unittests",
-        "content_browsertests",
-        "content_unittests",
-        "crypto_unittests",
-        "dbus_unittests",
-        "device_unittests",
-        "display_unittests",
-        "events_unittests",
-        "extensions_browsertests",
-        "extensions_unittests",
-        "gcm_unit_tests",
-        "gfx_unittests",
-        "gn_unittests",
-        "google_apis_unittests",
-        "gpu_unittests",
-        "interactive_ui_tests",
-        "ipc_tests",
-        "jingle_unittests",
-        "media_unittests",
-        "media_blink_unittests",
-        "mojo_unittests",
-        "nacl_loader_unittests",
-        "net_unittests",
-        "pdf_unittests",
-        "ppapi_unittests",
-        "printing_unittests",
-        "remoting_unittests",
-        "sandbox_linux_unittests",
-        "skia_unittests",
-        "sql_unittests",
-        "storage_unittests",
-        "sync_integration_tests",
-        "ui_base_unittests",
-        "ui_touch_selection_unittests",
-        "unit_tests",
-        "url_unittests",
-        "views_unittests",
-        "wm_unittests"
-      ]
-    }
-  },
-  'testers': {
-    'Win V8 FYI Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          # TODO(kbr): cut this bot over to Win10, coordinating with
-          # V8 team.
-          'gpu': WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Windows-2008ServerR2-SP1',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'win',
-    },
-    'Mac V8 FYI Release (Intel)': {
-      'swarming_dimensions': [
-        {
-          'gpu': '8086:0a2e',
-          'os': 'Mac-10.12.6',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'mac',
-    },
-    'Linux V8 FYI Release (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Linux V8 FYI Release - concurrent marking (NVIDIA)': {
-      'swarming_dimensions': [
-        {
-          'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-          'os': 'Ubuntu',
-          'pool': 'Chrome-GPU',
-        },
-      ],
-      'build_config': 'Release',
-      'swarming': True,
-      'os_type': 'linux',
-    },
-    'Android V8 FYI Release (Nexus 5X)': {
-      'swarming_dimensions': [
-        {
-          'device_type': 'bullhead',
-          'device_os': 'MMB29Q',
-          'os': 'Android'
-        },
-      ],
-      'build_config': 'android-chromium',
-      'swarming': True,
-      'os_type': 'android',
-    },
-  }
-}
-
-COMMON_GTESTS = {
-  'angle_deqp_egl_d3d11_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        # Run only on the Win10 Release NVIDIA 32- and 64-bit bots
-        # (and trybots) for the time being, at least until more capacity is
-        # added.
-        # TODO(jmadill): Run on the Linux Release NVIDIA bots.
-        'build_configs': ['Release', 'Release_x64'],
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          }
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_egl_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-d3d11',
-    ]
-  },
-  'angle_deqp_egl_gl_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'build_configs': ['Release', 'Release_x64'],
-        'swarming_dimension_sets': [
-          # NVIDIA Win 10
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          # Linux NVIDIA Quadro P400
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          # Mac Intel
-          {
-            'gpu': '8086:0a2e',
-            'os': 'Mac-10.12.6'
-          },
-          # Mac AMD
-          {
-            'gpu': '1002:6821',
-            'hidpi': '1',
-            'os': 'Mac-10.12.6'
-          },
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_egl_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gl',
-    ],
-  },
-  'angle_deqp_egl_gles_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        # Run on Nexus 5X swarmed bots.
-        'build_configs': ['android-chromium'],
-        'swarming_dimension_sets': [
-          # Nexus 5X
-          {
-            'device_type': 'bullhead',
-            'device_os': 'MMB29Q',
-            'os': 'Android',
-          }
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_egl_tests',
-    # Only pass the display type to desktop. The Android runner doesn't support
-    # passing args to the executable but only one display type is supported on
-    # Android anyways.
-    'desktop_args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gles',
-    ],
-    'android_args': [
-      '--enable-xml-result-parsing',
-      '--shard-timeout=500',
-    ],
-  },
-
-  'angle_deqp_gles2_d3d11_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          # NVIDIA Win 10
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          # AMD Win 7
-          {
-            'gpu': '1002:6613',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-          # Intel Win 10
-          {
-            'gpu': WIN10_INTEL_HD_630_STABLE_DRIVER,
-            'os': 'Windows-10',
-          },
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'desktop_swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_gles2_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-d3d11'
-    ]
-  },
-
-  'angle_deqp_gles2_gl_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          # Linux NVIDIA Quadro P400
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          # Linux Intel HD 630
-          {
-            'gpu': INTEL_HD_630,
-            'os': 'Ubuntu'
-          },
-          # Mac Intel
-          {
-            'gpu': '8086:0a2e',
-            'os': 'Mac-10.12.6'
-          },
-          # Mac AMD
-          {
-            'gpu': '1002:6821',
-            'hidpi': '1',
-            'os': 'Mac-10.12.6'
-          },
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'desktop_swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_gles2_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gl'
-    ]
-  },
-
-  'angle_deqp_gles2_gles_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        # Run on Nexus 5X swarmed bots.
-        'build_configs': ['android-chromium'],
-        'swarming_dimension_sets': [
-          # Nexus 5X
-          {
-            'device_type': 'bullhead',
-            'device_os': 'MMB29Q',
-            'os': 'Android'
-          }
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_gles2_tests',
-    'args': [
-      '--deqp-egl-display-type=angle-gles'
-    ],
-    'desktop_args': [
-      '--test-launcher-batch-limit=400',
-    ],
-    'android_args': [
-      '--enable-xml-result-parsing',
-      '--shard-timeout=500'
-    ],
-  },
-
-  'angle_deqp_gles2_vulkan_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          # NVIDIA Win 10
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          # AMD Win 7
-          {
-            'gpu': '1002:6613',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-          # NVIDIA Linux Quadro P400
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          # Qualcomm Android Adreno 418
-          {
-            'device_type': 'bullhead',
-            'device_os': 'O',
-            'os': 'Android',
-            'pool': 'Chrome-GPU',
-          },
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 4,
-    },
-    'test': 'angle_deqp_gles2_tests',
-    'args': [
-      '--deqp-egl-display-type=angle-vulkan'
-    ],
-    'desktop_args': [
-      '--test-launcher-batch-limit=400',
-    ],
-    'android_args': [
-      '--enable-xml-result-parsing',
-      '--shard-timeout=500'
-    ],
-  },
-
-  'angle_deqp_gles3_gles_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        # Run on Nexus 5X swarmed bots.
-        'build_configs': ['android-chromium'],
-        'swarming_dimension_sets': [
-          # Nexus 5X
-          {
-            'device_type': 'bullhead',
-            'device_os': 'MMB29Q',
-            'os': 'Android'
-          }
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 12,
-    },
-    'test': 'angle_deqp_gles3_tests',
-    # Only pass the display type to desktop. The Android runner doesn't support
-    # passing args to the executable but only one display type is supported on
-    # Android anyways.
-    'desktop_args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gles'
-    ],
-    'android_args': [
-      '--enable-xml-result-parsing',
-      '--shard-timeout=500'
-    ],
-  },
-
-  'angle_deqp_gles3_d3d11_tests': {
-    'tester_configs': [
-      {
-        # TODO(jmadill): Run this on ANGLE roll tryservers.
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          # NVIDIA Win 10
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          # AMD Win 7
-          # Temporarily disabled to prevent a recipe engine crash.
-          # TODO(jmadill): Re-enable when http://crbug.com713196 is fixed.
-          # {
-          #   'gpu': '1002:6613',
-          #   'os': 'Windows-2008ServerR2-SP1'
-          # },
-        ],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 12,
-    },
-    'test': 'angle_deqp_gles3_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-d3d11'
-    ]
-  },
-
-  'angle_deqp_gles3_gl_tests': {
-    'tester_configs': [
-      {
-        # TODO(jmadill): Run this on ANGLE roll tryservers.
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          # NVIDIA Linux Quadro P400
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          # Mac Intel
-          {
-            'gpu': '8086:0a2e',
-            'os': 'Mac-10.12.6'
-          },
-          # Mac AMD
-          {
-            'gpu': '1002:6821',
-            'hidpi': '1',
-            'os': 'Mac-10.12.6'
-          },
-        ],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 12,
-    },
-    'test': 'angle_deqp_gles3_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gl'
-    ]
-  },
-
-  'angle_deqp_gles31_d3d11_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          }
-        ],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 6,
-    },
-    'test': 'angle_deqp_gles31_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-d3d11'
-    ]
-  },
-
-  'angle_deqp_gles31_gl_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEQP,
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          }
-        ],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'swarming': {
-      'shards': 6,
-    },
-    'test': 'angle_deqp_gles31_tests',
-    'args': [
-      '--test-launcher-batch-limit=400',
-      '--deqp-egl-display-type=angle-gl'
-    ]
-  },
-
-  # Until we have more capacity, run angle_end2end_tests only on the
-  # FYI waterfall, the ANGLE trybots (which mirror the FYI waterfall),
-  # and the optional trybots (mainly used during ANGLE rolls).
-  'angle_end2end_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.FYI_AND_OPTIONAL_AND_WIN_ANGLE_AMD,
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          # TODO(ynovikov) Investigate why the test breaks on older devices.
-          'Android FYI Release (Nexus 5)',
-          'Android FYI Release (Nexus 6)',
-          'Android FYI Release (Nexus 9)',
-          # Temporarily disabled due to AMDGPU-PRO issues crbug.com/786219
-          'Linux FYI Release (AMD R7 240)',
-        ],
-      },
-    ],
-    'desktop_args': [
-      '--use-gpu-in-tests',
-      # ANGLE test retries deliberately disabled to prevent flakiness.
-      # http://crbug.com/669196
-      '--test-launcher-retry-limit=0'
-    ]
-  },
-  # white_box tests should run where end2end tests run
-  'angle_white_box_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.FYI_AND_OPTIONAL_AND_WIN_ANGLE_AMD,
-        'os_types': ['win', 'linux'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'desktop_args': [
-      # ANGLE test retries deliberately disabled to prevent flakiness.
-      # http://crbug.com/669196
-      '--test-launcher-retry-limit=0'
-    ]
-  },
-  'angle_unittests': {
-    'disabled_tester_configs': [
-      {
-        'names': [
-          # On Android, these are already run on the main waterfall.
-          # Run them on the one-off Android FYI bots, though.
-          'Android Release (Nexus 5X)',
-          'Android FYI Release (Nexus 5X)',
-        ],
-      },
-    ],
-    'desktop_args': [
-      '--use-gpu-in-tests',
-      # ANGLE test retries deliberately disabled to prevent flakiness.
-      # http://crbug.com/669196
-      '--test-launcher-retry-limit=0'
-    ],
-    'linux_args': [ '--no-xvfb' ]
-  },
-  'gl_tests': {
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-          # On Android, these are already run on the main waterfall.
-          # Run them on the one-off Android FYI bots, though.
-          'Android Release (Nexus 5X)',
-          'Android FYI Release (Nexus 5X)',
-        ],
-      },
-    ],
-    'desktop_args': [
-      '--use-gpu-in-tests',
-      '--use-cmd-decoder=validating',
-    ]
-  },
-  'gl_tests_passthrough': {
-    'tester_configs': [
-      {
-        'os_types': ['win'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'test': 'gl_tests',
-    'desktop_args': [
-      '--use-gpu-in-tests',
-      '--use-cmd-decoder=passthrough',
-     ]
-  },
-  'gl_unittests': {
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-          # On Android, these are already run on the main waterfall.
-          # Run them on the one-off Android FYI bots, though.
-          'Android Release (Nexus 5X)',
-          'Android FYI Release (Nexus 5X)',
-          # Temporarily disabled due to AMDGPU-PRO issues crbug.com/786219
-          'Linux FYI Release (AMD R7 240)',
-        ],
-      },
-    ],
-    'desktop_args': ['--use-gpu-in-tests'],
-    'linux_args': [ '--no-xvfb' ]
-  },
-  'gpu_unittests': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        # gpu_unittests is killing the Swarmed Linux GPU bots
-        # similarly to how content_unittests was:
-        # http://crbug.com/763498 .
-        'os_types': ['win', 'mac', 'android'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  # The gles1_conform_tests are closed-source and deliberately only run
-  # on the FYI waterfall.
-  'angle_gles1_conformance_tests': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and ANGLE tryservers.
-        'predicate': Predicates.FYI_ONLY,
-        'os_types': ['win'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'args': ['--use-gpu-in-tests', ]
-  },
-  # The gles2_conform_tests are closed-source and deliberately only run
-  # on the FYI waterfall and the optional tryservers.
-  'gles2_conform_test': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-      }
-    ],
-    # Don't run these tests on Android.
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-      {
-        'os_types': ['android'],
-      },
-    ],
-    'args': ['--use-gpu-in-tests']
-  },
-  'gles2_conform_d3d9_test': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['win'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'args': [
-      '--use-gpu-in-tests',
-      '--use-angle=d3d9',
-    ],
-    'test': 'gles2_conform_test',
-  },
-  'gles2_conform_gl_test': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['win'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'args': [
-      '--use-gpu-in-tests',
-      '--use-angle=gl',
-      '--disable-gpu-sandbox',
-    ],
-    'test': 'gles2_conform_test',
-  },
-  # Face and barcode detection unit tests, which currently only run on
-  # Mac OS, and require physical hardware.
-  'services_unittests': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['mac'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-      {
-        'swarming_dimension_sets': [
-          # These tests fail on the Mac Pros.
-          {
-            'gpu': '1002:679e',
-          },
-        ],
-      },
-    ],
-    'args': [
-      '--gtest_filter=*Detection*',
-      '--use-gpu-in-tests'
-    ]
-  },
-  'swiftshader_unittests': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['win', 'linux', 'mac'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Mac FYI Experimental Release (Intel)',
-          'Mac FYI Experimental Retina Release (AMD)',
-          'Mac FYI Experimental Retina Release (NVIDIA)',
-          'Mac Pro FYI Release (AMD)',
-        ],
-      },
-    ],
-  },
-  'tab_capture_end2end_tests': {
-    'tester_configs': [
-      {
-        'build_configs': ['Release', 'Release_x64'],
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    # Don't run these tests on Android.
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-        'os_types': ['android'],
-      },
-    ],
-    'args': [
-      '--enable-gpu',
-      '--test-launcher-bot-mode',
-      '--test-launcher-jobs=1',
-      '--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:' + \
-          'TabCaptureApiPixelTest.EndToEnd*'
-    ],
-    'linux_args': [ '--no-xvfb' ],
-    'test': 'browser_tests',
-  },
-  'video_decode_accelerator_d3d11_unittest': {
-    'tester_configs': [
-      {
-        'os_types': ['win']
-      },
-    ],
-    'args': [
-      '--use-angle=d3d11',
-      '--use-test-data-path',
-      '--test_video_data=test-25fps.h264:320:240:250:258:::1',
-    ],
-    'test': 'video_decode_accelerator_unittest',
-  },
-  'video_decode_accelerator_d3d9_unittest': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_ONLY,
-        'os_types': ['win']
-      },
-    ],
-    'args': [
-      '--use-angle=d3d9',
-      '--use-test-data-path',
-      '--test_video_data=test-25fps.h264:320:240:250:258:::1',
-    ],
-    'test': 'video_decode_accelerator_unittest',
-  },
-  'video_decode_accelerator_gl_unittest': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_ONLY,
-        'os_types': ['win']
-      },
-    ],
-    # Windows Intel doesn't have the GL extensions to support this test
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Win10 FYI Release (Intel HD 630)',
-          'Win10 FYI Exp Release (Intel HD 630)',
-        ],
-      },
-    ],
-    'args': [
-      '--use-angle=gl',
-      '--use-test-data-path',
-      '--test_video_data=test-25fps.h264:320:240:250:258:::1',
-    ],
-    'test': 'video_decode_accelerator_unittest',
-  },
-  'vr_browser_tests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_AND_OPTIONAL,
-        'os_types': ['win'],
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          {
-            'gpu': WIN10_INTEL_HD_630_STABLE_DRIVER,
-            'os': 'Windows-10',
-          }
-        ],
-      },
-    ],
-    'args': [
-      '--enable-gpu',
-      '--test-launcher-bot-mode',
-      '--test-launcher-jobs=1',
-      '--gtest_filter=VrBrowserTest*:XrBrowserTest*',
-      '--enable-pixel-output-in-tests',
-      '--gtest_also_run_disabled_tests',
-    ],
-    'test': 'browser_tests',
-  }
-}
-
-# This requires a hack because the isolate's name is different than
-# the executable's name. On the few non-swarmed testers, this causes
-# the executable to not be found. It would be better if the Chromium
-# recipe supported running isolates locally. crbug.com/581953
-
-NON_SWARMED_GTESTS = {
-  'tab_capture_end2end_tests': {
-    'swarming': {
-      'can_use_on_swarming_builders': False
-    },
-    # Don't run these tests on Android.
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-        'os_types': ['android'],
-      },
-    ],
-    'test': 'browser_tests',
-    'args': [
-      '--enable-gpu',
-      '--no-xvfb',
-      '--test-launcher-jobs=1',
-      '--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:' + \
-          'TabCaptureApiPixelTest.EndToEnd*'
-    ],
-    'swarming': {
-      'can_use_on_swarming_builders': False,
-    },
-  }
-}
-
-# These tests use Telemetry's new browser_test_runner, which is a much
-# simpler harness for correctness testing.
-TELEMETRY_GPU_INTEGRATION_TESTS = {
-  'context_lost': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'depth_capture': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'gpu_process_launch_tests': {
-    'target_name': 'gpu_process',
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'hardware_accelerated_feature': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'info_collection_tests': {
-    'target_name': 'info_collection',
-    'args': [
-      '--expected-vendor-id',
-      '${gpu_vendor_id}',
-      '--expected-device-id',
-      '${gpu_device_id}',
-    ],
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_AND_OPTIONAL,
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-
-          # The Mac ASAN swarming runs on two different GPU types so we can't
-          # have one expected vendor ID / device ID
-          'Mac FYI GPU ASAN Release',
-        ],
-      },
-    ],
-  },
-  'maps_pixel_test': {
-    'target_name': 'maps',
-    'args': [
-      '--dont-restore-color-profile-after-test',
-      '--os-type',
-      '${os_type}',
-      '--build-revision',
-      '${got_revision}',
-      '--test-machine-name',
-      '${buildername}',
-    ],
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'noop_sleep': {
-    'tester_configs': [
-      {
-        # Only run the noop sleep test on experimental configs.
-        'predicate': Predicates.EXPERIMENTAL_CONDITIONALLY,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-  },
-  'pixel_test': {
-    'target_name': 'pixel',
-    'args': [
-      '--dont-restore-color-profile-after-test',
-      '--refimg-cloud-storage-bucket',
-      'chromium-gpu-archive/reference-images',
-      '--os-type',
-      '${os_type}',
-      '--build-revision',
-      '${got_revision}',
-      '--test-machine-name',
-      '${buildername}',
-    ],
-    'non_precommit_args': [
-      '--upload-refimg-to-cloud-storage',
-    ],
-    'precommit_args': [
-      '--download-refimg-from-cloud-storage',
-    ],
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'screenshot_sync': {
-    'args': [
-      '--dont-restore-color-profile-after-test',
-    ],
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'trace_test': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-  },
-  'webgl_conformance': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT_PLUS_V8,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          'Android FYI 32 Vk Release (Nexus 5X)',
-          'Android FYI 64 Vk Release (Nexus 5X)',
-        ],
-      },
-    ],
-    'extra_browser_args': [
-      '--use-cmd-decoder=validating',
-    ],
-    'asan_args': ['--is-asan'],
-    'android_swarming': {
-      # On desktop platforms these don't take very long (~7 minutes),
-      # but on Android they take ~30 minutes and we want to shard them
-      # when sharding is available -- specifically on the Nexus 5X
-      # bots, which are currently the only Android configuration on
-      # the waterfalls where these tests are swarmed. If we had to
-      # restrict the sharding to certain Android devices, then we'd
-      # need some way to apply these Swarming parameters only to a
-      # subset of machines, like the way the tester_configs work.
-      'shards': 6,
-    },
-    'android_args': [
-      # The current working directory when run via isolate is
-      # out/Debug or out/Release. Reference this file relatively to
-      # it.
-      '--read-abbreviated-json-results-from=' + \
-      '../../content/test/data/gpu/webgl_conformance_tests_output.json',
-    ],
-    'swarming': {
-      'shards': 2,
-    },
-  },
-  'webgl_conformance_d3d9_tests': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['win'],
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          # TODO(jdarpinian): Re-enable when http://crbug.com/841789 is fixed.
-          'Win10 FYI Exp Release (Intel HD 630)',
-        ],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-angle=d3d9',
-      '--use-cmd-decoder=validating',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      'shards': 2,
-    },
-  },
-  'webgl_conformance_d3d9_passthrough': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall, optional tryservers, and Win
-        # ANGLE AMD tryserver.
-        'predicate': Predicates.FYI_AND_OPTIONAL_AND_WIN_ANGLE_AMD,
-        'os_types': ['win'],
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          # TODO(jdarpinian): Re-enable when http://crbug.com/841789 is fixed.
-          'Win10 FYI Exp Release (Intel HD 630)',
-        ],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-angle=d3d9',
-      '--use-cmd-decoder=passthrough',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      'shards': 2,
-    },
-  },
-  'webgl_conformance_d3d11_passthrough': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall, optional tryservers, and Win
-        # ANGLE AMD tryserver.
-        'predicate': Predicates.FYI_AND_OPTIONAL_AND_WIN_ANGLE_AMD,
-        'os_types': ['win'],
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-angle=d3d11',
-      '--use-cmd-decoder=passthrough',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      'shards': 2,
-    },
-  },
-  'webgl_conformance_gl_passthrough': {
-    'tester_configs': [
-      {
-        # Run this on the FYI waterfall and optional tryservers.
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        'os_types': ['linux', 'win'],
-        'disabled_instrumentation_types': ['tsan'],
-      }
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-      {
-        'swarming_dimension_sets': [
-          # crbug.com/555545 and crbug.com/649824:
-          # Disable webgl_conformance_gl_tests on some Win/AMD cards.
-          # Always fails on older cards, flaky on newer cards.
-          # Note that these must match the GPUs exactly; wildcard
-          # matches (i.e., only device ID) aren't supported!
-          {
-            'gpu': '1002:6779',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-          {
-            'gpu': '1002:6613',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-          # BUG 590951: Disable webgl_conformance_gl_tests on Win/Intel
-          {
-            'gpu': '8086:041a',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-          {
-            'gpu': '8086:0412',
-            'os': 'Windows-2008ServerR2-SP1'
-          },
-        ],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-gl=angle',
-      '--use-angle=gl',
-      '--use-cmd-decoder=passthrough',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      'shards': 2,
-    },
-  },
-  'webgl2_conformance_tests': {
-    'tester_configs': [
-      {
-         # The WebGL 2.0 conformance tests take over an hour to run on
-         # the Debug bots, which is too long.
-        'build_configs': ['Release', 'Release_x64'],
-        'predicate': Predicates.FYI_OPTIONAL_V8_AND_WIN_ANGLE_AMD,
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-
-          # The Mac NVIDIA Retina bots don't have the capacity to run
-          # this test suite on mac_optional_gpu_tests_rel.
-          'Optional Mac Retina Release (NVIDIA)',
-        ],
-        # Don't run these tests on Android yet.
-        'os_types': ['android'],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-cmd-decoder=validating',
-    ],
-    'args': [
-      '--webgl-conformance-version=2.0.1',
-      # The current working directory when run via isolate is
-      # out/Debug or out/Release. Reference this file relatively to
-      # it.
-      '--read-abbreviated-json-results-from=' + \
-      '../../content/test/data/gpu/webgl2_conformance_tests_output.json',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      # These tests currently take about an hour and fifteen minutes
-      # to run. Split them into roughly 5-minute shards.
-      'shards': 20,
-    },
-  },
-  'webgl2_conformance_gl_passthrough_tests': {
-    'tester_configs': [
-      {
-         # The WebGL 2.0 conformance tests take over an hour to run on
-         # the Debug bots, which is too long.
-        'build_configs': ['Release'],
-        'predicate': Predicates.FYI_ONLY,
-        # Run on the NVIDIA Release Windows/Linux and Intel Release Linux bots
-        'swarming_dimension_sets': [
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          {
-            'gpu': '8086:0412',
-            'os': 'Ubuntu'
-          },
-          {
-            'gpu': '8086:1912',
-            'os': 'Ubuntu'
-          },
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-        ],
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-gl=angle',
-      '--use-angle=gl',
-      '--use-cmd-decoder=passthrough',
-    ],
-    'args': [
-      '--webgl-conformance-version=2.0.1',
-      # The current working directory when run via isolate is
-      # out/Debug or out/Release. Reference this file relatively to
-      # it.
-      '--read-abbreviated-json-results-from=' + \
-      '../../content/test/data/gpu/webgl2_conformance_tests_output.json',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      # These tests currently take about an hour and fifteen minutes
-      # to run serially.
-      'shards': 20,
-    },
-  },
-  'webgl2_conformance_d3d11_passthrough_tests': {
-    'tester_configs': [
-      {
-         # The WebGL 2.0 conformance tests take over an hour to run on
-         # the Debug bots, which is too long.
-        'build_configs': ['Release'],
-        'predicate': Predicates.FYI_ONLY,
-        # Only run on the NVIDIA Release Windows bots.
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-        ],
-        'disabled_instrumentation_types': ['tsan'],
-      },
-    ],
-    'target_name': 'webgl_conformance',
-    'extra_browser_args': [
-      '--use-angle=d3d11',
-      '--use-cmd-decoder=passthrough',
-    ],
-    'args': [
-      '--webgl-conformance-version=2.0.1',
-      # The current working directory when run via isolate is
-      # out/Debug or out/Release. Reference this file relatively to
-      # it.
-      '--read-abbreviated-json-results-from=' + \
-      '../../content/test/data/gpu/webgl2_conformance_tests_output.json',
-    ],
-    'asan_args': ['--is-asan'],
-    'swarming': {
-      # These tests currently take about an hour and fifteen minutes
-      # to run. Split them into roughly 5-minute shards.
-      'shards': 20,
-    },
-  },
-  'viz_screenshot_sync': {
-    'target_name': 'screenshot_sync',
-    'args': [
-      '--dont-restore-color-profile-after-test',
-    ],
-    'extra_browser_args': [
-      # This test confirms that GPU compositing is working with OOP-D.
-      '--enable-features=VizDisplayCompositor',
-    ],
-    'tester_configs': [
-      {
-        'predicate': Predicates.DEFAULT,
-        'disabled_instrumentation_types': ['tsan'],
-        'os_types': ['win', 'linux'],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-        ],
-      },
-    ],
-  },
-}
-
-# These isolated tests don't use telemetry. They need to be placed in the
-# isolated_scripts section of the generated json.
-NON_TELEMETRY_ISOLATED_SCRIPT_TESTS = {
-  # We run perf tests on the CQ to ensure the tests don't crash.
-  'angle_perftests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        # Run on the Win/Linux Release NVIDIA bots and Android
-        'build_configs': ['Release', 'android-chromium'],
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-          {
-            'gpu': LINUX_QUADRO_P400_STABLE_DRIVER,
-            'os': 'Ubuntu'
-          },
-          {
-            'os': 'Android'
-          },
-        ],
-      },
-    ],
-    'disabled_tester_configs': [
-      {
-        'names': [
-          'Linux FYI Ozone (Intel)',
-          # anglebug.com/2433
-          'Android FYI Release (Nexus 6)',
-        ],
-      },
-    ],
-    'args': [
-      # Tell the tests to exit after one frame for faster iteration.
-      '--one-frame-only',
-    ],
-  },
-
-  'validating_command_buffer_perftests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        # Run on the Win Release NVIDIA bots.
-        # TODO(jmadill): Run on Linux bots when possible.
-        'build_configs': ['Release'],
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-        ],
-      },
-    ],
-    'isolate_name': 'command_buffer_perftests',
-    'args': [
-      '--use-cmd-decoder=validating',
-      '--use-stub',
-    ],
-  },
-
-  'passthrough_command_buffer_perftests': {
-    'tester_configs': [
-      {
-        'predicate': Predicates.FYI_AND_OPTIONAL,
-        # Run on the Win Release NVIDIA bots.
-        # TODO(jmadill): Run on Linux bots when possible.
-        'build_configs': ['Release'],
-        'swarming_dimension_sets': [
-          {
-            'gpu': NVIDIA_QUADRO_P400_ALL_DRIVERS,
-            'os': WIN10_NVIDIA_QUADRO_P400_STABLE_OS,
-          },
-        ],
-      },
-    ],
-    'isolate_name': 'command_buffer_perftests',
-    'args': [
-      '--use-cmd-decoder=passthrough',
-      '--use-angle=gl-null',
-    ],
-  },
-}
-
-def substitute_args(tester_config, args):
-  """Substitutes the variables in |args| from tester_config properties."""
-  substitutions = {
-    'os_type': tester_config['os_type'],
-    'gpu_vendor_id': '0',
-    'gpu_device_id': '0',
-  }
-
-  if 'gpu' in tester_config['swarming_dimensions'][0]:
-    # First remove the driver version, then split into vendor and device.
-    gpu = tester_config['swarming_dimensions'][0]['gpu']
-    gpu = gpu.split('-')[0].split(':')
-    substitutions['gpu_vendor_id'] = gpu[0]
-    substitutions['gpu_device_id'] = gpu[1]
-
-  return [string.Template(arg).safe_substitute(substitutions) for arg in args]
-
-def matches_swarming_dimensions(tester_config, dimension_sets):
-  for dimensions in dimension_sets:
-    for cur_dims in tester_config['swarming_dimensions']:
-      match = True
-      for key, value in dimensions.iteritems():
-        if key not in cur_dims:
-          match = False
-        elif value.endswith('*'):
-          if not cur_dims[key].startswith(value[0:-1]):
-            match = False
-        elif cur_dims[key] != value:
-          match = False
-      if match:
-        return True
-  return False
-
-def is_android(tester_config):
-  return tester_config['os_type'] == 'android'
-
-def is_linux(tester_config):
-  return tester_config['os_type'] == 'linux'
-
-def is_asan(tester_config):
-  return tester_config.get('is_asan', False)
-
-
-# Returns a list describing the type of this tester. It may include
-# both the type of the bot as well as the waterfall.
-def get_tester_type(tester_config):
-  result = []
-  if 'type' in tester_config:
-    result.append(tester_config['type'])
-  result.append(tester_config['parent']['type'])
-  return result
-
-def tester_config_matches_tester(tester_name, tester_config, tc,
-                                 check_waterfall):
-  if check_waterfall:
-    if not tc.get('predicate', Predicates.DEFAULT)(
-        get_tester_type(tester_config)):
-      return False
-
-  if 'names' in tc:
-    # Give priority to matching the tester_name.
-    if tester_name in tc['names']:
-      return True
-    if not tester_name in tc['names']:
-      return False
-  if 'os_types' in tc:
-    if not tester_config['os_type'] in tc['os_types']:
-      return False
-  if 'instrumentation_type' in tester_config:
-    if 'disabled_instrumentation_types' in tc:
-      if tester_config['instrumentation_type'] in \
-            tc['disabled_instrumentation_types']:
-        return False
-  if 'build_configs' in tc:
-    if not tester_config['build_config'] in tc['build_configs']:
-      return False
-  if 'swarming_dimension_sets' in tc:
-    if not matches_swarming_dimensions(tester_config,
-                                       tc['swarming_dimension_sets']):
-      return False
-  return True
-
-
-def experimental_config_matches_stable(waterfall, tester_config):
-  stable_tester_name = tester_config['stable_tester_name']
-  if not stable_tester_name in waterfall['testers']:
-    raise Exception('Stable config "' + stable_tester_name + '" not found')
-
-  stable_tester = waterfall['testers'][stable_tester_name]
-
-  stable_config = stable_tester['swarming_dimensions'][0]
-  experimental_config = tester_config['swarming_dimensions'][0]
-
-  return experimental_config == stable_config
-
-
-def is_test_config_experimental_conditionally(test_config):
-  if 'tester_configs' in test_config:
-    for tc in test_config['tester_configs']:
-      pred = tc.get('predicate', Predicates.DEFAULT)
-      if pred == Predicates.EXPERIMENTAL_CONDITIONALLY:
-        return True
-  return False
-
-def should_run_on_tester(waterfall, tester_name, tester_config, test_config):
-  # Special case for experimental tester configs. Don't run tests by default
-  # if the experimental config matches the stable config.
-  if Types.EXPERIMENTAL in get_tester_type(tester_config):
-    # TODO(kbr): there's a bug here where if the experimental bot
-    # doesn't match the stable bot, it runs too many tests because it
-    # doesn't obey the test's predicate.
-    is_conditional = is_test_config_experimental_conditionally(test_config)
-    if experimental_config_matches_stable(waterfall, tester_config):
-      return is_conditional
-    else:
-      return not is_conditional
-
-  # Check if this config is disabled on this tester
-  if 'disabled_tester_configs' in test_config:
-    for dtc in test_config['disabled_tester_configs']:
-      if tester_config_matches_tester(tester_name, tester_config, dtc, False):
-        return False
-  if 'tester_configs' in test_config:
-    for tc in test_config['tester_configs']:
-      if tester_config_matches_tester(tester_name, tester_config, tc, True):
-        return True
-    return False
-  else:
-    # If tester_configs is unspecified, run nearly all tests by default,
-    # but let tester_config_matches_tester filter out any undesired
-    # tests, such as ones that should only run on the Optional bots.
-    return tester_config_matches_tester(tester_name, tester_config, {}, True)
-
-def remove_tester_configs_from_result(result):
-  if 'tester_configs' in result:
-    # Don't print the tester_configs in the JSON.
-    result.pop('tester_configs')
-  if 'disabled_tester_configs' in result:
-    # Don't print the disabled_tester_configs in the JSON.
-    result.pop('disabled_tester_configs')
-
-def add_common_test_properties(test, tester_config):
-  if tester_config.get('use_gpu_trigger_script'):
-    test['trigger_script'] = {
-      'script': '//testing/trigger_scripts/trigger_multiple_dimensions.py',
-      'args': [
-        '--multiple-trigger-configs',
-        json.dumps(tester_config['swarming_dimensions'] +
-                   tester_config.get('alternate_swarming_dimensions', [])),
-        '--multiple-dimension-script-verbose',
-        'True'
-      ],
-    }
-
-def generate_gtest(waterfall, tester_name, tester_config, test, test_config):
-  if not should_run_on_tester(
-      waterfall, tester_name, tester_config, test_config):
-    return None
-  result = copy.deepcopy(test_config)
-  if 'test' in result:
-    result['name'] = test
-  else:
-    result['test'] = test
-  if (not tester_config['swarming']) and test in NON_SWARMED_GTESTS:
-    # Need to override this result.
-    result = copy.deepcopy(NON_SWARMED_GTESTS[test])
-    result['name'] = test
-  else:
-    if not 'swarming' in result:
-      result['swarming'] = {}
-    result['swarming'].update({
-      'can_use_on_swarming_builders': tester_config['swarming'],
-      'dimension_sets': tester_config['swarming_dimensions']
-    })
-    if 'swarming_settings' in tester_config:
-      result['swarming'].update(tester_config['swarming_settings'])
-    if is_android(tester_config):
-      # Integrate with the unified logcat system.
-      result['swarming'].update({
-        'cipd_packages': [
-          {
-            'cipd_package': 'infra/tools/luci/logdog/butler/${platform}',
-            'location': 'bin',
-            'revision': 'git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c'
-          }
-        ],
-        'output_links': [
-          {
-            'link': [
-              'https://luci-logdog.appspot.com/v/?s',
-              '=android%2Fswarming%2Flogcats%2F',
-              '${TASK_ID}%2F%2B%2Funified_logcats'
-            ],
-            'name': 'shard #${SHARD_INDEX} logcats'
-          }
-        ]
-      })
-
-  def add_conditional_args(key, fn):
-    if key in result:
-      if fn(tester_config):
-        if not 'args' in result:
-          result['args'] = []
-        result['args'] += result[key]
-      # Don't put the conditional args in the JSON.
-      result.pop(key)
-
-  add_conditional_args('desktop_args', lambda cfg: not is_android(cfg))
-  add_conditional_args('linux_args', is_linux)
-  add_conditional_args('android_args', is_android)
-
-  if 'desktop_swarming' in result:
-    if not is_android(tester_config):
-      result['swarming'].update(result['desktop_swarming'])
-    # Don't put the desktop_swarming in the JSON.
-    result.pop('desktop_swarming')
-  # Remove the tester_configs and disabled_tester_configs, if present,
-  # from the result.
-  remove_tester_configs_from_result(result)
-
-  add_common_test_properties(result, tester_config)
-  return result
-
-def generate_gtests(waterfall, tester_name, tester_config, test_dictionary):
-  # The relative ordering of some of the tests is important to
-  # minimize differences compared to the handwritten JSON files, since
-  # Python's sorts are stable and there are some tests with the same
-  # key (see gles2_conform_d3d9_test and similar variants). Avoid
-  # losing the order by avoiding coalescing the dictionaries into one.
-  gtests = []
-  for test_name, test_config in sorted(test_dictionary.iteritems()):
-    test = generate_gtest(waterfall, tester_name, tester_config,
-                          test_name, test_config)
-    if test:
-      # generate_gtest may veto the test generation on this platform.
-      gtests.append(test)
-  return gtests
-
-def generate_isolated_test(waterfall, tester_name, tester_config, test,
-                           test_config, extra_browser_args, isolate_name,
-                           prefix_args):
-  if not should_run_on_tester(waterfall, tester_name, tester_config,
-                              test_config):
-    return None
-  test_args = ['-v']
-  extra_browser_args_string = ""
-  if extra_browser_args != None:
-    extra_browser_args_string += ' '.join(extra_browser_args)
-  if 'extra_browser_args' in test_config:
-    extra_browser_args_string += ' ' + ' '.join(
-        test_config['extra_browser_args'])
-  if extra_browser_args_string != "":
-    test_args.append('--extra-browser-args=' + extra_browser_args_string)
-  if 'args' in test_config:
-    test_args.extend(substitute_args(tester_config, test_config['args']))
-  if 'desktop_args' in test_config and not is_android(tester_config):
-    test_args.extend(substitute_args(tester_config,
-                                     test_config['desktop_args']))
-  if 'android_args' in test_config and is_android(tester_config):
-    test_args.extend(substitute_args(tester_config,
-                                     test_config['android_args']))
-  if 'asan_args' in test_config and is_asan(tester_config):
-    test_args.extend(substitute_args(tester_config,
-                                     test_config['asan_args']))
-  # The step name must end in 'test' or 'tests' in order for the
-  # results to automatically show up on the flakiness dashboard.
-  # (At least, this was true some time ago.) Continue to use this
-  # naming convention for the time being to minimize changes.
-  step_name = test
-  if not (step_name.endswith('test') or step_name.endswith('tests')):
-    step_name = '%s_tests' % step_name
-  # Prepend GPU-specific flags.
-  swarming = {
-    'can_use_on_swarming_builders': tester_config['swarming'],
-    'dimension_sets': tester_config['swarming_dimensions']
-  }
-  if 'swarming_settings' in tester_config:
-    swarming.update(tester_config['swarming_settings'])
-  if 'swarming' in test_config:
-    swarming.update(test_config['swarming'])
-  if 'android_swarming' in test_config and is_android(tester_config):
-    swarming.update(test_config['android_swarming'])
-  result = {
-    'args': prefix_args + test_args,
-    'isolate_name': isolate_name,
-    'name': step_name,
-    'swarming': swarming,
-  }
-  if 'non_precommit_args' in test_config:
-    result['non_precommit_args'] = test_config['non_precommit_args']
-  if 'precommit_args' in test_config:
-    result['precommit_args'] = test_config['precommit_args']
-  add_common_test_properties(result, tester_config)
-  return result
-
-def generate_telemetry_test(waterfall, tester_name, tester_config,
-                            test, test_config):
-  extra_browser_args = ['--enable-logging=stderr', '--js-flags=--expose-gc']
-  benchmark_name = test_config.get('target_name') or test
-  prefix_args = [
-    benchmark_name,
-    '--show-stdout',
-    '--browser=%s' % tester_config['build_config'].lower(),
-    # --passthrough displays more of the logging in Telemetry when run
-    # --via typ, in particular some of the warnings about tests being
-    # --expected to fail, but passing.
-    '--passthrough',
-  ]
-  return generate_isolated_test(waterfall, tester_name, tester_config, test,
-                                test_config, extra_browser_args,
-                                'telemetry_gpu_integration_test',
-                                prefix_args)
-
-def generate_telemetry_tests(waterfall, tester_name, tester_config,
-                             test_dictionary):
-  isolated_scripts = []
-  for test_name, test_config in sorted(test_dictionary.iteritems()):
-    test = generate_telemetry_test(waterfall,
-      tester_name, tester_config, test_name, test_config)
-    if test:
-      isolated_scripts.append(test)
-  return isolated_scripts
-
-def generate_non_telemetry_isolated_test(waterfall, tester_name, tester_config,
-                                         test, test_config):
-  isolate_name = test
-  if 'isolate_name' in test_config:
-    isolate_name = test_config['isolate_name']
-  return generate_isolated_test(waterfall, tester_name, tester_config, test,
-                                test_config, None, isolate_name, [])
-
-def generate_non_telemetry_isolated_tests(waterfall, tester_name, tester_config,
-                                          test_dictionary):
-  isolated_scripts = []
-  for test_name, test_config in sorted(test_dictionary.iteritems()):
-    test = generate_non_telemetry_isolated_test(waterfall,
-      tester_name, tester_config, test_name, test_config)
-    if test:
-      isolated_scripts.append(test)
-  return isolated_scripts
-
-def install_parent_links(waterfall):
-  # Make the testers point back to the top-level waterfall so that we
-  # can ask about its properties when determining whether a given test
-  # should run on a given waterfall.
-  for name, config in waterfall.get('testers', {}).iteritems():
-    config['parent'] = waterfall
-
-def cmp_gtests(a, b):
-  # Prefer to compare based on the "test" key.
-  val = cmp(a['test'], b['test'])
-  if val != 0:
-    return val
-  if 'name' in a and 'name' in b:
-    return cmp(a['name'], b['name']) # pragma: no cover
-  if 'name' not in a and 'name' not in b:
-    return 0 # pragma: no cover
-  # Prefer to put variants of the same test after the first one.
-  if 'name' in a:
-    return 1
-  # 'name' is in b.
-  return -1 # pragma: no cover
-
-def generate_all_tests(waterfall, filename):
-  tests = {}
-  for builder, config in waterfall.get('prologue', {}).iteritems():
-    tests[builder] = config
-  for builder, config in waterfall.get('builders', {}).iteritems():
-    tests[builder] = config
-  for name, config in waterfall['testers'].iteritems():
-    gtests = generate_gtests(waterfall, name, config, COMMON_GTESTS)
-    isolated_scripts = \
-      generate_telemetry_tests(waterfall,
-        name, config, TELEMETRY_GPU_INTEGRATION_TESTS) + \
-      generate_non_telemetry_isolated_tests(waterfall, name, config,
-        NON_TELEMETRY_ISOLATED_SCRIPT_TESTS)
-    tests[name] = {}
-    if len(gtests) > 0:
-      tests[name]['gtest_tests'] = sorted(gtests, cmp=cmp_gtests)
-    if len(isolated_scripts) > 0:
-      tests[name]['isolated_scripts'] = sorted(
-        isolated_scripts, key=lambda x: x['name'])
-  tests['AAAAA1 AUTOGENERATED FILE DO NOT EDIT'] = {}
-  tests['AAAAA2 See gpu/generate_buildbot_json.py to make changes'] = {}
-  with open(os.path.join(SRC_DIR, 'testing', 'buildbot', filename), 'wb') as fp:
-    json.dump(tests, fp, indent=2, separators=(',', ': '), sort_keys=True)
-    fp.write('\n')
-
-def main():
-  install_parent_links(FYI_WATERFALL)
-  install_parent_links(WATERFALL)
-  install_parent_links(V8_FYI_WATERFALL)
-
-  generate_all_tests(FYI_WATERFALL, 'chromium.gpu.fyi.json')
-  generate_all_tests(WATERFALL, 'chromium.gpu.json')
-  generate_all_tests(V8_FYI_WATERFALL, 'client.v8.fyi.json')
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main())
diff --git a/content/test/gpu/gpu_tests/cloud_storage_integration_test_base.py b/content/test/gpu/gpu_tests/cloud_storage_integration_test_base.py
index 42e00e7..c720891 100644
--- a/content/test/gpu/gpu_tests/cloud_storage_integration_test_base.py
+++ b/content/test/gpu/gpu_tests/cloud_storage_integration_test_base.py
@@ -222,9 +222,9 @@
     if cls._reference_image_parameters:
       return
     browser = cls.browser
-    if not browser.supports_system_info:
-      raise Exception('System info must be supported by the browser')
     system_info = browser.GetSystemInfo()
+    if not system_info:
+      raise Exception('System info must be supported by the browser')
     if not system_info.gpu:
       raise Exception('GPU information was absent')
     device = system_info.gpu.devices[0]
diff --git a/content/test/gpu/gpu_tests/context_lost_expectations.py b/content/test/gpu/gpu_tests/context_lost_expectations.py
index 6c60e91..40d9db9 100644
--- a/content/test/gpu/gpu_tests/context_lost_expectations.py
+++ b/content/test/gpu/gpu_tests/context_lost_expectations.py
@@ -54,12 +54,3 @@
     self.Fail('ContextLost_WebGLContextLostFromQuantity',
               ['android', ('qualcomm', 'Adreno (TM) 420')], bug=611906)
 
-    # Android WebGLBlocked/Unblocked
-    self.Fail('ContextLost_WebGLBlockedAfterJSNavigation',
-              ['android', 'nvidia'], bug=832886)
-    self.Flaky('ContextLost_WebGLBlockedAfterJSNavigation',
-              ['android', 'qualcomm'], bug=832886)
-    self.Fail('ContextLost_WebGLUnblockedAfterUserInitiatedReload',
-              ['android', 'nvidia'], bug=832886)
-    self.Flaky('ContextLost_WebGLUnblockedAfterUserInitiatedReload',
-              ['android', 'qualcomm'], bug=832886)
diff --git a/content/test/gpu/gpu_tests/context_lost_integration_test.py b/content/test/gpu/gpu_tests/context_lost_integration_test.py
index d23dff5..615442f3 100644
--- a/content/test/gpu/gpu_tests/context_lost_integration_test.py
+++ b/content/test/gpu/gpu_tests/context_lost_integration_test.py
@@ -162,7 +162,8 @@
         self.fail('Test failed (context not restored properly?)')
 
   def _CheckCrashCount(self, tab, expected_kills):
-    if not tab.browser.supports_system_info:
+    system_info = tab.browser.GetSystemInfo()
+    if not system_info:
       self.fail('Browser must support system info')
 
     if not tab.EvaluateJavaScript(
diff --git a/content/test/gpu/gpu_tests/gpu_process_integration_test.py b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
index 6bfd4a5..a252a49 100644
--- a/content/test/gpu/gpu_tests/gpu_process_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
@@ -224,9 +224,9 @@
     self.RestartBrowserIfNecessaryWithArgs([])
     self._NavigateAndWait(test_path)
     tab = self.tab
-    if not tab.browser.supports_system_info:
-      self.fail('Browser must support system info')
     system_info = tab.browser.GetSystemInfo()
+    if not system_info:
+      self.fail('Browser must support system info')
     if not system_info.gpu:
       self.fail('Target machine must have a GPU')
     if not system_info.gpu.aux_attributes:
@@ -410,9 +410,10 @@
       if 'SwiftShader' not in renderer:
         self.fail('Expected SwiftShader renderer; instead got ' + renderer)
       # Validate GPU info.
-      if not self.browser.supports_system_info:
+      system_info = self.browser.GetSystemInfo()
+      if not system_info:
         self.fail("Browser doesn't support GetSystemInfo")
-      gpu = self.browser.GetSystemInfo().gpu
+      gpu = system_info.gpu
       if not gpu:
         self.fail('Target machine must have a GPU')
       if not gpu.aux_attributes:
diff --git a/content/test/gpu/gpu_tests/gpu_test_expectations.py b/content/test/gpu/gpu_tests/gpu_test_expectations.py
index d0ba9cd7..5bdc731 100644
--- a/content/test/gpu/gpu_tests/gpu_test_expectations.py
+++ b/content/test/gpu/gpu_tests/gpu_test_expectations.py
@@ -114,8 +114,7 @@
       self._cached_system_info = None
 
     if self._cached_system_info is None:
-      if browser.supports_system_info:
-        self._cached_system_info = browser.GetSystemInfo()
+      self._cached_system_info = browser.GetSystemInfo()
 
     if self._cached_system_info is not None:
       gpu_info = self._cached_system_info.gpu
diff --git a/content/test/gpu/gpu_tests/gpu_test_expectations_unittest.py b/content/test/gpu/gpu_tests/gpu_test_expectations_unittest.py
index b13e632..9afb0bae 100644
--- a/content/test/gpu/gpu_tests/gpu_test_expectations_unittest.py
+++ b/content/test/gpu/gpu_tests/gpu_test_expectations_unittest.py
@@ -45,10 +45,6 @@
       }
     self.system_info = system_info.SystemInfo.FromDict(sys_info)
 
-  @property
-  def supports_system_info(self):
-    return False if not self.system_info else True
-
   def GetSystemInfo(self):
     return self.system_info
 
diff --git a/content/test/gpu/gpu_tests/info_collection_test.py b/content/test/gpu/gpu_tests/info_collection_test.py
index c256a05..4035362 100644
--- a/content/test/gpu/gpu_tests/info_collection_test.py
+++ b/content/test/gpu/gpu_tests/info_collection_test.py
@@ -39,10 +39,11 @@
     self.tab.action_runner.Navigate('chrome:gpu')
 
     # Gather the IDs detected by the GPU process
-    if not self.browser.supports_system_info:
+    system_info = self.browser.GetSystemInfo()
+    if not system_info:
       self.fail("Browser doesn't support GetSystemInfo")
 
-    gpu = self.browser.GetSystemInfo().gpu.devices[0]
+    gpu = system_info.gpu.devices[0]
     if not gpu:
       self.fail("System Info doesn't have a gpu")
 
diff --git a/content/test/gpu/gpu_tests/test_expectations_unittest.py b/content/test/gpu/gpu_tests/test_expectations_unittest.py
index 2977e94..4096d43 100644
--- a/content/test/gpu/gpu_tests/test_expectations_unittest.py
+++ b/content/test/gpu/gpu_tests/test_expectations_unittest.py
@@ -22,10 +22,6 @@
     self.platform = platform
     self.browser_type = browser_type
 
-  @property
-  def supports_system_info(self):
-    return False
-
 
 class SampleExpectationSubclass(test_expectations.Expectation):
   def __init__(self, expectation, pattern, conditions=None, bug=None):
diff --git a/content/test/gpu/gpu_tests/verify_all_expectations_unittest.py b/content/test/gpu/gpu_tests/verify_all_expectations_unittest.py
index 263e367e..3802d26 100644
--- a/content/test/gpu/gpu_tests/verify_all_expectations_unittest.py
+++ b/content/test/gpu/gpu_tests/verify_all_expectations_unittest.py
@@ -59,10 +59,6 @@
       gl_renderer, passthrough_cmd_decoder)
     self.browser_type = browser_type
 
-  @property
-  def supports_system_info(self):
-    return True
-
   def GetSystemInfo(self):
     return self._system_info
 
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 0b087c19..65cc8bed 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -120,9 +120,6 @@
         'in-parameter-passed-as-inout-argument-and-global.html',
         ['nvidia'], bug=792210)
 
-    self.Skip('conformance2/rendering/blitframebuffer-size-overflow.html',
-        ['intel', 'amd', 'no_angle'], bug=844308)
-
     # Windows only.
     self.Fail('conformance2/buffers/uniform-buffers.html',
         ['win'], bug=757098)
@@ -211,7 +208,7 @@
     self.Fail('deqp/functional/gles3/shaderpackingfunction.html',
         ['win', 'nvidia', 'opengl'], bug=795030)
     self.Skip('conformance2/rendering/blitframebuffer-size-overflow.html',
-        ['win', 'nvidia', 'opengl'], bug=830046)
+        ['win', 'nvidia', 'opengl', 'passthrough'], bug=830046)
     self.Flaky('conformance2/transform_feedback/switching-objects.html',
         ['win', 'nvidia', 'opengl', 'no_passthrough'], bug=832238)
 
@@ -442,10 +439,6 @@
     self.Fail('deqp/functional/gles3/negativeshaderapi.html',
         ['mac', 'amd', 'intel'], bug=811614)
 
-    # Mac NVIDIA
-    self.Skip('conformance2/rendering/blitframebuffer-size-overflow.html',
-        ['mac', 'nvidia', 'no_angle'], bug=844308)
-
     # Mac Retina NVIDIA
     self.Fail('deqp/functional/gles3/shaderindexing/mat_01.html',
         ['mac', ('nvidia', 0xfe9)], bug=728271)
@@ -841,7 +834,7 @@
     # Linux NVIDIA Quadro P400
     # This test causes a lost device and then the next test fails.
     self.Skip('conformance2/rendering/blitframebuffer-size-overflow.html',
-        ['linux', ('nvidia', 0x1cb3)], bug=709320)
+        ['linux', ('nvidia', 0x1cb3), 'passthrough'], bug=709320)
     # Observed flaky on Swarmed bots. Some of these were directly
     # observed, some not. We can't afford any flakes on the tryservers
     # so mark them all flaky.
diff --git a/docs/gpu/gpu_testing.md b/docs/gpu/gpu_testing.md
index 5b05f8a..b4a925e 100644
--- a/docs/gpu/gpu_testing.md
+++ b/docs/gpu/gpu_testing.md
@@ -76,8 +76,8 @@
 <!-- XXX: broken link -->
 [new-testing-infra]: https://github.com/luci/luci-py/wiki
 [isolated-testing-infra]: https://www.chromium.org/developers/testing/isolated-testing/infrastructure
-[chromium.gpu]: https://build.chromium.org/p/chromium.gpu/console
-[chromium.gpu.fyi]: https://build.chromium.org/p/chromium.gpu.fyi/console
+[chromium.gpu]: https://ci.chromium.org/p/chromium/g/chromium.gpu/console
+[chromium.gpu.fyi]: https://ci.chromium.org/p/chromium/g/chromium.gpu.fyi/console
 [tools/build workspace]: https://code.google.com/p/chromium/codesearch#chromium/build/scripts/slave/recipe_modules/chromium_tests/chromium_gpu_fyi.py
 [bots-presentation]: https://docs.google.com/presentation/d/1BC6T7pndSqPFnituR7ceG7fMY7WaGqYHhx5i9ECa8EI/edit?usp=sharing
 
@@ -408,27 +408,33 @@
 
 These files are autogenerated by the following script:
 
-*   [`generate_buildbot_json.py`](https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/generate_buildbot_json.py)
+*   [`generate_buildbot_json.py`](https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/generate_buildbot_json.py)
 
-This script is completely self-contained and should hopefully be
-self-explanatory. The JSON files are parsed by the chromium and chromium_trybot
-recipes, and describe two types of tests:
+This script is documented in
+[`testing/buildbot/README.md`](https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/README.md). The
+JSON files are parsed by the chromium and chromium_trybot recipes, and describe
+two basic types of tests:
 
 *   GTests: those which use the Googletest and Chromium's `base/test/launcher/`
     frameworks.
-*   Telemetry based tests: those which are built on the Telemetry framework and
-    launch the entire browser.
+*   Isolated scripts: tests whose initial entry point is a Python script which
+    follows a simple convention of command line argument parsing.
+
+The majority of the GPU tests are however:
+
+*   Telemetry based tests: an isolated script test which is built on the
+    Telemetry framework and which launches the entire browser.
 
 A prerequisite of adding a new test to the bots is that that test [run via
-isolates][new-isolates]. Once that is done, modify `generate_buildbot_json.py` to add the
-test to the appropriate set of bots. Be careful when adding large new test
-steps to all of the bots, because the GPU bots are a limited resource and do
-not currently have the capacity to absorb large new test suites. It is safer to
-get new tests running on the chromium.gpu.fyi waterfall first, and expand from
-there to the chromium.gpu waterfall (which will also make them run against
-every Chromium CL by virtue of the `linux_chromium_rel_ng`,
-`mac_chromium_rel_ng` and `win_chromium_rel_ng` tryservers' mirroring of the
-bots on this waterfall – so be careful!).
+isolates][new-isolates]. Once that is done, modify `test_suites.pyl` to add the
+test to the appropriate set of bots. Be careful when adding large new test steps
+to all of the bots, because the GPU bots are a limited resource and do not
+currently have the capacity to absorb large new test suites. It is safer to get
+new tests running on the chromium.gpu.fyi waterfall first, and expand from there
+to the chromium.gpu waterfall (which will also make them run against every
+Chromium CL by virtue of the `linux_chromium_rel_ng`, `mac_chromium_rel_ng`,
+`win7_chromium_rel_ng` and `android-marshmallow-arm64-rel` tryservers' mirroring
+of the bots on this waterfall – so be careful!).
 
 Tryjobs which add new test steps to the chromium.gpu.json file will run those
 new steps during the tryjob, which helps ensure that the new test won't break
diff --git a/docs/gpu/gpu_testing_bot_details.md b/docs/gpu/gpu_testing_bot_details.md
index 50b5cfe..d0c869f 100644
--- a/docs/gpu/gpu_testing_bot_details.md
+++ b/docs/gpu/gpu_testing_bot_details.md
@@ -199,13 +199,13 @@
         build.
 *   [`src/tools/mb/mb_config.pyl`][mb_config.pyl]
     *   Defines the GN arguments for all of the bots.
-*   [`src/content/test/gpu/generate_buildbot_json.py`][generate_buildbot_json.py]
-    *   The generator script for `chromium.gpu.json` and
+*   [`src/testing/buildbot/generate_buildbot_json.py`][generate_buildbot_json.py]
+    *   The generator script for all the waterfalls, including `chromium.gpu.json` and
         `chromium.gpu.fyi.json`. It defines on which GPUs various tests run.
-    *   It's completely self-contained and should hopefully be fairly
-        comprehensible.
+    *   See the [README for generate_buildbot_json.py] for documentation
+        on this script and the descriptions of the waterfalls and test suites.
     *   When modifying this script, don't forget to also run it, to regenerate
-        the JSON files.
+        the JSON files. Don't worry; the presubmit step will catch this if you forget.
     *   See [Adding new steps to the GPU bots] for more details.
 
 [chromium/src]:              https://chromium.googlesource.com/chromium/src/
@@ -214,7 +214,9 @@
 [chromium.gpu.fyi.json]:     https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/chromium.gpu.fyi.json
 [gn_isolate_map.pyl]:        https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/gn_isolate_map.pyl
 [mb_config.pyl]:             https://chromium.googlesource.com/chromium/src/+/master/tools/mb/mb_config.pyl
-[generate_buildbot_json.py]: https://chromium.googlesource.com/chromium/src/+/master/content/test/gpu/generate_buildbot_json.py
+[generate_buildbot_json.py]: https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/generate_buildbot_json.py
+[waterfalls.pyl]:            https://chromium.googlesource.com/chromium/src/+/master/testing/buildbot/waterfalls.pyl
+[README for generate_buildbot_json.py]: ../../testing/buildbot/README.md
 
 In the [infradata/config] workspace (Google internal only, sorry):
 
@@ -292,15 +294,12 @@
 
 1.  Create a CL in the Chromium workspace which does the following. Here's an
     [example CL](https://chromium-review.googlesource.com/1041164).
-    1.  Adds the new machines to
-        `src/content/test/gpu/generate_buildbot_json.py`.
+    1.  Adds the new machines to [waterfalls.pyl].
         1.  The swarming dimensions are crucial. These must match the GPU and
             OS type of the physical hardware in the Swarming pool. This is what
             causes the VMs to spawn their tests on the correct hardware. Make
             sure to use the Chrome-GPU pool, and that the new machines were
             specifically added to that pool.
-        1.  Make sure to set the `swarming` property to `True` for both the
-            Release and Debug bots.
         1.  Make triply sure that there are no collisions between the new
             hardware you're adding and hardware already in the Swarming pool.
             For example, it used to be the case that all of the Windows NVIDIA
@@ -312,11 +311,12 @@
             data center). Similarly, the Win8 bots had to have a very precise
             OS description (`Windows-2012ServerR2-SP0`).
         1.  If you're deploying a new bot that's similar to another existing
-            configuration, please search around in the file for references to
+            configuration, please search around in
+            `src/testing/buildbot/test_suite_exceptions.pyl` for references to
             the other bot's name and see if your new bot needs to be added to
             any exclusion lists. For example, some of the tests don't run on
             certain Win bots because of missing OpenGL extensions.
-        1.  Run this script to regenerate
+        1.  Run [generate_buildbot_json.py] to regenerate
             `src/testing/buildbot/chromium.gpu.fyi.json`.
     1. Updates [`cr-buildbucket.cfg`][cr-buildbucket.cfg]:
         *   Add the two new machines (Release and Debug) inside the
@@ -549,7 +549,7 @@
 1.  Create a CL in the Chromium workspace:
     1.  Add your new bot (for example, "Optional Win7 Release
         (CoolNewGPUType)") to the chromium.gpu.fyi waterfall in
-        [generate_buildbot_json.py]. (Note, this is a bad example: the
+        [waterfalls.pyl]. (Note, this is a bad example: the
         "optional" bots have special semantics in this script. You'd probably
         want to define some new category of bot if you didn't intend to add
         this to `win_optional_gpu_tests_rel`.) 
@@ -584,7 +584,7 @@
 
 1.  Make sure that all of the current Swarming jobs for this OS and GPU
     configuration are targeted at the "stable" version of the driver in
-    `src/testing/gpu/generate_buildbot_json.py`.
+    [waterfalls.pyl].
 1.  File a `Build Infrastructure` bug, component `Infra>Labs`, to have ~4 of the
     physical machines already in the Swarming pool upgraded to the new version
     of the driver.
@@ -593,14 +593,14 @@
     waterfall](#How-to-add-a-new-tester-bot-to-the-chromium_gpu_fyi-waterfall)
     to deploy one.
 1.  Have this experimental bot target the new version of the driver in
-    `src/testing/gpu/generate_buildbot_json.py`.
+    [waterfalls.pyl].
 1.  Hopefully, the new machine will pass the pixel tests. If it doesn't, then
     unfortunately, it'll be necessary to follow the instructions on
     [updating the pixel tests] to temporarily suppress the failures on this
     particular configuration. Keep the time window for these test suppressions
     as narrow as possible.
 1.  Watch the new machine for a day or two to make sure it's stable.
-1.  When it is, update `src/testing/gpu/generate_buildbot_json.py` to use the
+1.  When it is, update [waterfalls.pyl] to use the
     "gpu trigger script" functionality to select *either* the stable *or* the
     new driver version on the stable version of the bot. See [this
     CL](https://chromium-review.googlesource.com/882344) for an example, though
@@ -611,7 +611,7 @@
 1.  If necessary, update pixel test expectations and remove the suppressions
     added above.
 1.  Remove the alternate swarming dimensions for the stable bot from
-    `generate_buildbot_json.py`, locking it to the new driver version.
+    [waterfalls.pyl], locking it to the new driver version.
 
 Note that we leave the experimental bot in place. We could reclaim it, but it
 seems worthwhile to continuously test the "next" version of graphics drivers as
diff --git a/docs/gpu/pixel_wrangling.md b/docs/gpu/pixel_wrangling.md
index 18afaa3..94d7773 100644
--- a/docs/gpu/pixel_wrangling.md
+++ b/docs/gpu/pixel_wrangling.md
@@ -91,7 +91,9 @@
 *   `gl_tests`: see `src/gpu/BUILD.gn`
 *   `gl_unittests`: see `src/ui/gl/BUILD.gn`
 
-And more. See `src/content/test/gpu/generate_buildbot_json.py` for the
+And more. See
+[`src/testing/buildbot/README.md`](../../testing/buildbot/README.md)
+and the GPU sections of `test_suites.pyl` and `waterfalls.pyl` for the
 complete description of bots and tests.
 
 Additionally, the Release bots run:
diff --git a/extensions/shell/app/app-Info.plist b/extensions/shell/app/app-Info.plist
index e415a0f..a31859c 100644
--- a/extensions/shell/app/app-Info.plist
+++ b/extensions/shell/app/app-Info.plist
@@ -25,7 +25,7 @@
 	<key>NSPrincipalClass</key>
 	<string>NSApplication</string>
 	<key>LSMinimumSystemVersion</key>
-	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSFileQuarantineEnabled</key>
 	<true/>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
diff --git a/extensions/shell/app/helper-Info.plist b/extensions/shell/app/helper-Info.plist
index ccd54a59..7ce23b2c 100644
--- a/extensions/shell/app/helper-Info.plist
+++ b/extensions/shell/app/helper-Info.plist
@@ -21,7 +21,7 @@
 	<key>LSFileQuarantineEnabled</key>
 	<true/>
 	<key>LSMinimumSystemVersion</key>
-	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSUIElement</key>
 	<string>1</string>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index d37241b3..029b86f 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -364,6 +364,7 @@
     "command_buffer/service/renderbuffer_manager_unittest.cc",
     "command_buffer/service/scheduler_unittest.cc",
     "command_buffer/service/service_discardable_manager_unittest.cc",
+    "command_buffer/service/service_transfer_cache_unittest.cc",
     "command_buffer/service/shader_manager_unittest.cc",
     "command_buffer/service/shader_translator_cache_unittest.cc",
     "command_buffer/service/shader_translator_unittest.cc",
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index bbe3137..4b1e104 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -8566,22 +8566,27 @@
     return;
   }
 
+  base::CheckedNumeric<GLint> src_width_temp = srcX1;
+  src_width_temp -= srcX0;
+  base::CheckedNumeric<GLint> src_height_temp = srcY1;
+  src_height_temp -= srcY0;
+  base::CheckedNumeric<GLint> dst_width_temp = dstX1;
+  dst_width_temp -= dstX0;
+  base::CheckedNumeric<GLint> dst_height_temp = dstY1;
+  dst_height_temp -= dstY0;
+  if (!src_width_temp.Abs().IsValid() || !src_height_temp.Abs().IsValid() ||
+      !dst_width_temp.Abs().IsValid() || !dst_height_temp.Abs().IsValid()) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name,
+                       "the width or height of src or dst region overflowed");
+    return;
+  }
 
   if (workarounds().adjust_src_dst_region_for_blitframebuffer) {
     gfx::Size read_size = GetBoundReadFramebufferSize();
     gfx::Rect src_bounds(0, 0, read_size.width(), read_size.height());
     GLint src_x = srcX1 > srcX0 ? srcX0 : srcX1;
     GLint src_y = srcY1 > srcY0 ? srcY0 : srcY1;
-    base::CheckedNumeric<GLint> src_width_temp = srcX1;
-    src_width_temp -= srcX0;
-    base::CheckedNumeric<GLint> src_height_temp = srcY1;
-    src_height_temp -= srcY0;
     GLuint src_width = 0, src_height = 0;
-    if (!src_width_temp.IsValid() || !src_height_temp.IsValid()) {
-      LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name,
-                         "the width or height of src region overflow");
-      return;
-    }
     if (!src_width_temp.Abs().AssignIfValid(&src_width))
       src_width = 0;
     if (!src_height_temp.Abs().AssignIfValid(&src_height))
diff --git a/gpu/command_buffer/service/service_transfer_cache.cc b/gpu/command_buffer/service/service_transfer_cache.cc
index faea324d..5a13fe4 100644
--- a/gpu/command_buffer/service/service_transfer_cache.cc
+++ b/gpu/command_buffer/service/service_transfer_cache.cc
@@ -4,16 +4,28 @@
 
 #include "gpu/command_buffer/service/service_transfer_cache.h"
 
+#include "base/bind.h"
+#include "base/memory/memory_coordinator_client_registry.h"
 #include "base/sys_info.h"
 
 namespace gpu {
 namespace {
-size_t CacheSizeLimit() {
-  if (base::SysInfo::IsLowEndDevice()) {
-    return 4 * 1024 * 1024;
-  } else {
-    return 128 * 1024 * 1024;
+size_t CacheSizeLimit(base::MemoryState state) {
+  size_t normal_state_memory_usage = 128 * 1024 * 1024;
+  if (base::SysInfo::IsLowEndDevice())
+    normal_state_memory_usage = 4 * 1024 * 1024;
+
+  switch (state) {
+    case base::MemoryState::NORMAL:
+      return normal_state_memory_usage;
+    case base::MemoryState::THROTTLED:
+      return normal_state_memory_usage / 2;
+    case base::MemoryState::SUSPENDED:
+      return 0u;
+    case base::MemoryState::UNKNOWN:
+      NOTREACHED();
   }
+  return normal_state_memory_usage;
 }
 
 }  // namespace
@@ -23,7 +35,7 @@
     std::unique_ptr<cc::ServiceTransferCacheEntry> entry)
     : handle(handle), entry(std::move(entry)) {}
 
-ServiceTransferCache::CacheEntryInternal::~CacheEntryInternal() = default;
+ServiceTransferCache::CacheEntryInternal::~CacheEntryInternal() {}
 
 ServiceTransferCache::CacheEntryInternal::CacheEntryInternal(
     CacheEntryInternal&& other) = default;
@@ -34,9 +46,16 @@
 
 ServiceTransferCache::ServiceTransferCache()
     : entries_(EntryCache::NO_AUTO_EVICT),
-      cache_size_limit_(CacheSizeLimit()) {}
+      cache_size_limit_(CacheSizeLimit(memory_state_)),
+      memory_pressure_listener_(
+          base::BindRepeating(&ServiceTransferCache::OnMemoryPressure,
+                              base::Unretained(this))) {
+  base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this);
+}
 
-ServiceTransferCache::~ServiceTransferCache() = default;
+ServiceTransferCache::~ServiceTransferCache() {
+  base::MemoryCoordinatorClientRegistry::GetInstance()->Unregister(this);
+}
 
 bool ServiceTransferCache::CreateLockedEntry(
     cc::TransferCacheEntryType entry_type,
@@ -130,4 +149,21 @@
   }
 }
 
+void ServiceTransferCache::OnMemoryPressure(
+    base::MemoryPressureListener::MemoryPressureLevel level) {
+  if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL)
+    OnPurgeMemory();
+}
+
+void ServiceTransferCache::OnMemoryStateChange(base::MemoryState state) {
+  memory_state_ = state;
+  cache_size_limit_ = CacheSizeLimit(memory_state_);
+}
+
+void ServiceTransferCache::OnPurgeMemory() {
+  cache_size_limit_ = 0u;
+  EnforceLimits();
+  cache_size_limit_ = CacheSizeLimit(memory_state_);
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/service_transfer_cache.h b/gpu/command_buffer/service/service_transfer_cache.h
index 883d0eb..71b4761 100644
--- a/gpu/command_buffer/service/service_transfer_cache.h
+++ b/gpu/command_buffer/service/service_transfer_cache.h
@@ -9,6 +9,8 @@
 
 #include "base/containers/mru_cache.h"
 #include "base/containers/span.h"
+#include "base/memory/memory_coordinator_client.h"
+#include "base/memory/memory_pressure_listener.h"
 #include "cc/paint/transfer_cache_entry.h"
 #include "gpu/command_buffer/common/discardable_handle.h"
 #include "gpu/command_buffer/service/context_group.h"
@@ -24,10 +26,11 @@
 // unlocking and deleting entries when no longer needed, as well as enforcing
 // cache limits. If the cache exceeds its specified limits, unlocked transfer
 // cache entries may be deleted.
-class GPU_GLES2_EXPORT ServiceTransferCache {
+class GPU_GLES2_EXPORT ServiceTransferCache
+    : public base::MemoryCoordinatorClient {
  public:
   ServiceTransferCache();
-  ~ServiceTransferCache();
+  ~ServiceTransferCache() override;
 
   bool CreateLockedEntry(cc::TransferCacheEntryType entry_type,
                          uint32_t entry_id,
@@ -41,14 +44,21 @@
   cc::ServiceTransferCacheEntry* GetEntry(cc::TransferCacheEntryType entry_type,
                                           uint32_t entry_id);
 
+  // base::MemoryCoordinatorClient implementation.
+  void OnMemoryStateChange(base::MemoryState state) override;
+  void OnPurgeMemory() override;
+
   // Test-only functions:
   void SetCacheSizeLimitForTesting(size_t cache_size_limit) {
     cache_size_limit_ = cache_size_limit;
     EnforceLimits();
   }
+  size_t cache_size_for_testing() const { return total_size_; }
 
  private:
   void EnforceLimits();
+  void OnMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel level);
 
   struct CacheEntryInternal {
     CacheEntryInternal(base::Optional<ServiceDiscardableHandle> handle,
@@ -64,6 +74,8 @@
                      CacheEntryInternal>;
   EntryCache entries_;
 
+  base::MemoryState memory_state_ = base::MemoryState::NORMAL;
+
   // Total size of all |entries_|. The same as summing
   // GpuDiscardableEntry::size for each entry.
   size_t total_size_ = 0;
@@ -71,6 +83,8 @@
   // The limit above which the cache will start evicting resources.
   size_t cache_size_limit_ = 0;
 
+  base::MemoryPressureListener memory_pressure_listener_;
+
   DISALLOW_COPY_AND_ASSIGN(ServiceTransferCache);
 };
 
diff --git a/gpu/command_buffer/service/service_transfer_cache_unittest.cc b/gpu/command_buffer/service/service_transfer_cache_unittest.cc
new file mode 100644
index 0000000..e0549e1
--- /dev/null
+++ b/gpu/command_buffer/service/service_transfer_cache_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+
+#include "gpu/command_buffer/service/service_transfer_cache.h"
+
+#include "cc/paint/raw_memory_transfer_cache_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gpu {
+namespace {
+
+std::unique_ptr<cc::ServiceTransferCacheEntry> CreateEntry(size_t size) {
+  auto entry = std::make_unique<cc::ServiceRawMemoryTransferCacheEntry>();
+  std::vector<uint8_t> data(size, 0u);
+  entry->Deserialize(nullptr, data);
+  return entry;
+}
+
+TEST(ServiceTransferCacheTest, EnforcesOnPurgeMemory) {
+  ServiceTransferCache cache;
+  uint32_t entry_id = 0u;
+  size_t entry_size = 1024u;
+
+  cache.CreateLocalEntry(++entry_id, CreateEntry(entry_size));
+  EXPECT_EQ(cache.cache_size_for_testing(), entry_size);
+  cache.OnPurgeMemory();
+  EXPECT_EQ(cache.cache_size_for_testing(), 0u);
+}
+
+TEST(ServiceTransferCacheTest, EnforcesMemoryStateSUSPENDED) {
+  ServiceTransferCache cache;
+  uint32_t entry_id = 0u;
+  size_t entry_size = 1024u;
+
+  // Nothing should be cached in suspended state.
+  cache.OnMemoryStateChange(base::MemoryState::SUSPENDED);
+  cache.CreateLocalEntry(++entry_id, CreateEntry(entry_size));
+  EXPECT_EQ(cache.cache_size_for_testing(), 0u);
+}
+
+TEST(ServiceTransferCacheTest, EnforcesMemoryStateNORMAL) {
+  ServiceTransferCache cache;
+  uint32_t entry_id = 0u;
+  size_t entry_size = 1024u;
+
+  // Normal state, keep what's in the cache.
+  cache.CreateLocalEntry(++entry_id, CreateEntry(entry_size));
+  EXPECT_EQ(cache.cache_size_for_testing(), entry_size);
+  cache.OnMemoryStateChange(base::MemoryState::NORMAL);
+  cache.CreateLocalEntry(++entry_id, CreateEntry(entry_size));
+  EXPECT_EQ(cache.cache_size_for_testing(), entry_size * 2);
+}
+
+}  // namespace
+}  // namespace gpu
diff --git a/gpu/config/gpu_info_collector_win.cc b/gpu/config/gpu_info_collector_win.cc
index ba4e62b4..c75b3ef 100644
--- a/gpu/config/gpu_info_collector_win.cc
+++ b/gpu/config/gpu_info_collector_win.cc
@@ -113,11 +113,14 @@
 #if defined(GOOGLE_CHROME_BUILD) && defined(OFFICIAL_BUILD)
 // This function has a real implementation for official builds that can
 // be found in src/third_party/amd.
-void GetAMDVideocardInfo(GPUInfo* gpu_info);
+bool GetAMDSwitchableInfo(bool* is_switchable,
+                          uint32_t* active_vendor_id,
+                          uint32_t* active_device_id);
 #else
-void GetAMDVideocardInfo(GPUInfo* gpu_info) {
-  DCHECK(gpu_info);
-  return;
+bool GetAMDSwitchableInfo(bool* is_switchable,
+                          uint32_t* active_vendor_id,
+                          uint32_t* active_device_id) {
+  return false;
 }
 #endif
 
@@ -206,25 +209,17 @@
   SetupDiDestroyDeviceInfoList(device_info);
   if (found_amd && found_intel) {
     // Potential AMD Switchable system found.
-    {
-      // TODO(zmo): for GetAMDVideocardInfo, now we only care about
-      // |amd_switchable| bit. Simplify the logic of that function and then the
-      // logic here.
-      GPUInfo amd_gpu_info;
-      for (const auto& device : devices) {
-        if (device.vendor_id == 0x8086) {
-          amd_gpu_info.gpu = device;
-        }
-      }
-      GetAMDVideocardInfo(&amd_gpu_info);
-      gpu_info->amd_switchable = amd_gpu_info.amd_switchable;
-      if (!gpu_info->amd_switchable && !amd_is_primary) {
-        // Some machines aren't properly detected as AMD switchable, but count
-        // them anyway. This may erroneously count machines where there are
-        // independent AMD and Intel cards and the AMD isn't hooked up to
-        // anything, but that should be rare.
-        gpu_info->amd_switchable = true;
-      }
+    if (!amd_is_primary) {
+      // Some machines aren't properly detected as AMD switchable, but count
+      // them anyway. This may erroneously count machines where there are
+      // independent AMD and Intel cards and the AMD isn't hooked up to
+      // anything, but that should be rare.
+      gpu_info->amd_switchable = true;
+    } else {
+      bool is_amd_switchable = false;
+      uint32_t active_vendor = 0, active_device = 0;
+      GetAMDSwitchableInfo(&is_amd_switchable, &active_vendor, &active_device);
+      gpu_info->amd_switchable = is_amd_switchable;
     }
   }
   bool found = false;
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index ed16269d..d5593b85 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -433,6 +433,7 @@
     ":gen_devtools_client_api",
     ":protocol_sources",
     ":version_header",
+    "//base:base_static",
     "//components/cookie_config",
     "//components/security_state/core",
     "//content/public/common",
diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc
index 5d1ddc56..dc18c05 100644
--- a/headless/lib/headless_content_main_delegate.cc
+++ b/headless/lib/headless_content_main_delegate.cc
@@ -208,6 +208,8 @@
 
 void HeadlessContentMainDelegate::InitCrashReporter(
     const base::CommandLine& command_line) {
+  if (command_line.HasSwitch(::switches::kDisableBreakpad))
+    return;
 #if defined(OS_FUCHSIA)
   // TODO(fuchsia): Implement this when crash reporting/Breakpad are available
   // in Fuchsia. (crbug.com/753619)
diff --git a/ios/chrome/browser/ui/bookmarks/BUILD.gn b/ios/chrome/browser/ui/bookmarks/BUILD.gn
index eabd3b2b..bcdf6fa 100644
--- a/ios/chrome/browser/ui/bookmarks/BUILD.gn
+++ b/ios/chrome/browser/ui/bookmarks/BUILD.gn
@@ -186,9 +186,11 @@
     "//ios/chrome/browser/ui/authentication:eg_test_support",
     "//ios/chrome/browser/ui/bookmarks:bookmarks",
     "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/popup_menu:constants",
     "//ios/chrome/browser/ui/toolbar/buttons",
     "//ios/chrome/browser/ui/toolbar/legacy",
     "//ios/chrome/browser/ui/toolbar/public",
+    "//ios/chrome/browser/ui/tools_menu/public",
     "//ios/chrome/test/app:test_support",
     "//ios/chrome/test/earl_grey:test_support",
     "//ios/public/provider/chrome/browser/signin:test_support",
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
index d847580..6a36874 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
@@ -17,14 +17,18 @@
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/experimental_flags.h"
 #include "ios/chrome/browser/pref_names.h"
 #import "ios/chrome/browser/ui/authentication/signin_earlgrey_utils.h"
 #import "ios/chrome/browser/ui/authentication/signin_promo_view.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_path_cache.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
+#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #import "ios/chrome/browser/ui/toolbar/buttons/toolbar_constants.h"
 #import "ios/chrome/browser/ui/toolbar/legacy/toolbar_controller_constants.h"
+#include "ios/chrome/browser/ui/tools_menu/public/tools_menu_constants.h"
+#include "ios/chrome/browser/ui/ui_util.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/app/bookmarks_test_util.h"
@@ -144,14 +148,6 @@
                     nil);
 }
 
-// Matcher for the button to close the tools menu.
-id<GREYMatcher> CloseToolsMenuButton() {
-  NSString* closeMenuButtonText =
-      l10n_util::GetNSString(IDS_IOS_TOOLBAR_CLOSE_MENU);
-  return grey_allOf(grey_accessibilityID(kToolbarToolsMenuButtonIdentifier),
-                    grey_accessibilityLabel(closeMenuButtonText), nil);
-}
-
 }  // namespace
 
 // Bookmark integration tests for Chrome.
@@ -185,7 +181,10 @@
 
 // Verifies that adding a bookmark and removing a bookmark via the UI properly
 // updates the BookmarkModel.
-- (void)testAddRemoveBookmark {
+- (void)testAddRemoveBookmarkLegacy {
+  if (IsUIRefreshPhase1Enabled()) {
+    EARL_GREY_TEST_SKIPPED(@"This test is non UIRefresh only.");
+  }
   const GURL bookmarkedURL = web::test::HttpServer::MakeUrl(
       "http://ios/testing/data/http_server_files/pony.html");
   std::string expectedURLContent = bookmarkedURL.GetContent();
@@ -234,13 +233,83 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(kStarUnlitLabel)]
       assertWithMatcher:grey_notNil()];
 
-  // TODO(crbug.com/617652): This code should be removed when a common helper
-  // is added to close any menus, which should be run as test setup.
+  // Close the opened tab.
+  [chrome_test_util::BrowserCommandDispatcherForMainBVC() closeCurrentTab];
+}
+
+// Verifies that adding a bookmark and removing a bookmark via the UI properly
+// updates the BookmarkModel.
+- (void)testAddRemoveBookmark {
+  if (!IsUIRefreshPhase1Enabled()) {
+    EARL_GREY_TEST_SKIPPED(@"This test is UIRefresh only.");
+  }
+  const GURL bookmarkedURL = web::test::HttpServer::MakeUrl(
+      "http://ios/testing/data/http_server_files/pony.html");
+  std::string expectedURLContent = bookmarkedURL.GetContent();
+  NSString* bookmarkTitle = @"my bookmark";
+
+  [ChromeEarlGrey loadURL:bookmarkedURL];
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
+                                          expectedURLContent)]
+      assertWithMatcher:grey_notNil()];
+
+  // Add the bookmark from the UI.
+  [BookmarksTestCase bookmarkCurrentTabWithTitle:bookmarkTitle];
+
+  // Verify the bookmark is set.
+  [BookmarksTestCase assertBookmarksWithTitle:bookmarkTitle expectedCount:1];
+
+  // Verify the star is lit.
+  if (!IsCompactWidth()) {
+    [[EarlGrey
+        selectElementWithMatcher:grey_accessibilityLabel(
+                                     l10n_util::GetNSString(IDS_TOOLTIP_STAR))]
+        assertWithMatcher:grey_notNil()];
+  }
+
+  // Open the BookmarkEditor.
   if (IsCompactWidth()) {
-    [[EarlGrey selectElementWithMatcher:CloseToolsMenuButton()]
+    [ChromeEarlGreyUI openToolsMenu];
+    [[[EarlGrey
+        selectElementWithMatcher:grey_allOf(grey_accessibilityID(
+                                                kToolsMenuEditBookmark),
+                                            grey_sufficientlyVisible(), nil)]
+           usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
+        onElementWithMatcher:grey_accessibilityID(
+                                 kPopupMenuToolsMenuTableViewId)]
+        performAction:grey_tap()];
+  } else {
+    [[EarlGrey
+        selectElementWithMatcher:grey_accessibilityLabel(
+                                     l10n_util::GetNSString(IDS_TOOLTIP_STAR))]
         performAction:grey_tap()];
   }
 
+  // Delete the Bookmark.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kBookmarkEditDeleteButtonIdentifier)]
+      performAction:grey_tap()];
+
+  // Verify the bookmark is not in the BookmarkModel.
+  [BookmarksTestCase assertBookmarksWithTitle:bookmarkTitle expectedCount:0];
+
+  // Verify the the page is no longer bookmarked.
+  if (IsCompactWidth()) {
+    [ChromeEarlGreyUI openToolsMenu];
+    [[[EarlGrey
+        selectElementWithMatcher:grey_allOf(grey_accessibilityID(
+                                                kToolsMenuAddToBookmarks),
+                                            grey_sufficientlyVisible(), nil)]
+           usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
+        onElementWithMatcher:grey_accessibilityID(
+                                 kPopupMenuToolsMenuTableViewId)]
+        assertWithMatcher:grey_notNil()];
+  } else {
+    [[EarlGrey
+        selectElementWithMatcher:grey_accessibilityLabel(
+                                     l10n_util::GetNSString(IDS_TOOLTIP_STAR))]
+        assertWithMatcher:grey_notNil()];
+  }
   // Close the opened tab.
   [chrome_test_util::BrowserCommandDispatcherForMainBVC() closeCurrentTab];
 }
@@ -553,8 +622,19 @@
     [[EarlGrey selectElementWithMatcher:StarButton()] performAction:grey_tap()];
   } else {
     [ChromeEarlGreyUI openToolsMenu];
-    [[EarlGrey selectElementWithMatcher:LitStarButtoniPhone()]
-        performAction:grey_tap()];
+    if (IsUIRefreshPhase1Enabled()) {
+      [[[EarlGrey
+          selectElementWithMatcher:grey_allOf(grey_accessibilityID(
+                                                  kToolsMenuEditBookmark),
+                                              grey_sufficientlyVisible(), nil)]
+             usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
+          onElementWithMatcher:grey_accessibilityID(
+                                   kPopupMenuToolsMenuTableViewId)]
+          performAction:grey_tap()];
+    } else {
+      [[EarlGrey selectElementWithMatcher:LitStarButtoniPhone()]
+          performAction:grey_tap()];
+    }
   }
   GREYAssertTrue(chrome_test_util::GetRegisteredKeyCommandsCount() == 0,
                  @"No keyboard commands are registered.");
@@ -2327,7 +2407,9 @@
       selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                           grey_sufficientlyVisible(), nil)]
       performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Cancel")]
+  [[EarlGrey
+      selectElementWithMatcher:grey_allOf(grey_buttonTitle(@"Cancel"),
+                                          grey_sufficientlyVisible(), nil)]
       performAction:grey_tap()];
 
   // Check that the bookmarks UI reappeared and the cell is still here.
@@ -3107,12 +3189,25 @@
 
 // Adds a bookmark for the current tab. Must be called when on a tab.
 + (void)starCurrentTab {
-  if (!IsCompactWidth()) {
-    [[EarlGrey selectElementWithMatcher:StarButton()] performAction:grey_tap()];
-  } else {
+  if (IsUIRefreshPhase1Enabled()) {
     [ChromeEarlGreyUI openToolsMenu];
-    [[EarlGrey selectElementWithMatcher:AddBookmarkButton()]
+    [[[EarlGrey
+        selectElementWithMatcher:grey_allOf(grey_accessibilityID(
+                                                kToolsMenuAddToBookmarks),
+                                            grey_sufficientlyVisible(), nil)]
+           usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
+        onElementWithMatcher:grey_accessibilityID(
+                                 kPopupMenuToolsMenuTableViewId)]
         performAction:grey_tap()];
+  } else {
+    if (!IsCompactWidth()) {
+      [[EarlGrey selectElementWithMatcher:StarButton()]
+          performAction:grey_tap()];
+    } else {
+      [ChromeEarlGreyUI openToolsMenu];
+      [[EarlGrey selectElementWithMatcher:AddBookmarkButton()]
+          performAction:grey_tap()];
+    }
   }
 }
 
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h
index 5f9ce60..5c9122bb 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.h
@@ -40,6 +40,8 @@
 
 // Initializers.
 - (instancetype)init NS_DESIGNATED_INITIALIZER;
+// Initializes the view controller without the recently closed tabs section.
+- (instancetype)initWithoutRecentlyClosedSection;
 - (instancetype)initWithTableViewStyle:(UITableViewStyle)style
                            appBarStyle:
                                (ChromeTableViewControllerStyle)appBarStyle
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index 63901e7..afa77d08 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -83,8 +83,6 @@
 NSString* const kOtherDeviceCollapsedKey = @"OtherDevicesCollapsed";
 // Key for saving whether the Recently Closed section is collapsed.
 NSString* const kRecentlyClosedCollapsedKey = @"RecentlyClosedCollapsed";
-// There are 2 static sections before the first SessionSection.
-int const kNumberOfSectionsBeforeSessions = 1;
 // Estimated Table Row height.
 const CGFloat kEstimatedRowHeight = 56;
 // The UI displays relative time for up to this number of hours and then
@@ -109,6 +107,10 @@
 @property(nonatomic, strong) SigninPromoViewMediator* signinPromoViewMediator;
 // The sectionIdentifier for the last tapped header, 0 if no header was tapped.
 @property(nonatomic, assign) NSInteger lastTappedHeaderSectionIdentifier;
+// YES if recently closed section should be displayed.
+@property(nonatomic, assign, readonly) BOOL shouldDisplayRecentlyClosedSection;
+// If there is a recently closed section, there is 1 section before sessions.
+@property(nonatomic, assign, readonly) NSInteger numberOfSectionsBeforeSessions;
 @end
 
 @implementation RecentTabsTableViewController : ChromeTableViewController
@@ -124,6 +126,8 @@
 @synthesize sessionState = _sessionState;
 @synthesize signinPromoViewMediator = _signinPromoViewMediator;
 @synthesize tabRestoreService = _tabRestoreService;
+@synthesize shouldDisplayRecentlyClosedSection =
+    _shouldDisplayRecentlyClosedSection;
 
 #pragma mark - Public Interface
 
@@ -134,6 +138,14 @@
     _sessionState = SessionsSyncUserState::USER_SIGNED_OUT;
     _syncedSessions.reset(new synced_sessions::SyncedSessions());
     _lastTappedHeaderSectionIdentifier = 0;
+    _shouldDisplayRecentlyClosedSection = YES;
+  }
+  return self;
+}
+
+- (instancetype)initWithoutRecentlyClosedSection {
+  if (self = [self init]) {
+    _shouldDisplayRecentlyClosedSection = NO;
   }
   return self;
 }
@@ -173,11 +185,16 @@
   self.title = l10n_util::GetNSString(IDS_IOS_CONTENT_SUGGESTIONS_RECENT_TABS);
 }
 
+- (NSInteger)numberOfSectionsBeforeSessions {
+  return self.shouldDisplayRecentlyClosedSection ? 1 : 0;
+}
+
 #pragma mark - TableViewModel
 
 - (void)loadModel {
   [super loadModel];
-  [self addRecentlyClosedSection];
+  if (self.shouldDisplayRecentlyClosedSection)
+    [self addRecentlyClosedSection];
 
   if (self.sessionState ==
       SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
@@ -315,9 +332,9 @@
   // SectionIdentifier could've been deleted previously, do not rely on these
   // being in sequential order at this point.
   NSInteger sectionIdentifierToRemove = kFirstSessionSectionIdentifier;
-  NSInteger sectionToDelete = kNumberOfSectionsBeforeSessions;
+  NSInteger sectionToDelete = self.numberOfSectionsBeforeSessions;
   while ([self.tableViewModel numberOfSections] >
-         kNumberOfSectionsBeforeSessions) {
+         self.numberOfSectionsBeforeSessions) {
     if ([self.tableViewModel
             hasSectionForSectionIdentifier:sectionIdentifierToRemove]) {
       [self.tableView
@@ -490,7 +507,7 @@
 - (NSIndexSet*)sessionSectionIndexSet {
   // Create a range of all Session Sections.
   NSRange rangeOfSessionSections =
-      NSMakeRange(kNumberOfSectionsBeforeSessions, [self numberOfSessions]);
+      NSMakeRange(self.numberOfSectionsBeforeSessions, [self numberOfSessions]);
   NSIndexSet* sessionSectionIndexes =
       [NSIndexSet indexSetWithIndexesInRange:rangeOfSessionSections];
   return sessionSectionIndexes;
@@ -558,7 +575,8 @@
 }
 
 - (void)refreshRecentlyClosedTabs {
-  [self.tableView reloadData];
+  if (self.shouldDisplayRecentlyClosedSection)
+    [self.tableView reloadData];
 }
 
 - (void)setTabRestoreService:(sessions::TabRestoreService*)tabRestoreService {
@@ -712,7 +730,8 @@
   NSInteger sectionIdentifer =
       [self.tableViewModel sectionIdentifierForSection:section];
   DCHECK([self isSessionSectionIdentifier:sectionIdentifer]);
-  return _syncedSessions->GetSession(section - kNumberOfSectionsBeforeSessions);
+  return _syncedSessions->GetSession(section -
+                                     self.numberOfSectionsBeforeSessions);
 }
 
 - (synced_sessions::DistantTab const*)distantTabAtIndexPath:
@@ -976,7 +995,7 @@
       syncService->GetOpenTabsUIDelegate();
 
   [self.tableViewModel removeSectionWithIdentifier:sectionIdentifier];
-  _syncedSessions->EraseSession(section - kNumberOfSectionsBeforeSessions);
+  _syncedSessions->EraseSession(section - self.numberOfSectionsBeforeSessions);
 
   void (^tableUpdates)(void) = ^{
     [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:section]
diff --git a/ios/chrome/browser/ui/settings/cells/BUILD.gn b/ios/chrome/browser/ui/settings/cells/BUILD.gn
index 37b2570..76d4507 100644
--- a/ios/chrome/browser/ui/settings/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/cells/BUILD.gn
@@ -12,6 +12,8 @@
     "byo_textfield_item.mm",
     "card_multiline_item.h",
     "card_multiline_item.mm",
+    "clear_browsing_data_constants.h",
+    "clear_browsing_data_constants.mm",
     "copied_to_chrome_item.h",
     "copied_to_chrome_item.mm",
     "encryption_item.h",
diff --git a/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h b/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h
new file mode 100644
index 0000000..7940960
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CLEAR_BROWSING_DATA_CONSTANTS_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CLEAR_BROWSING_DATA_CONSTANTS_H_
+
+#import <Foundation/Foundation.h>
+
+// The accessibility identifier of the privacy settings collection view.
+extern NSString* const kClearBrowsingDataCollectionViewAccessibilityIdentifier;
+
+// The accessibility identifiers of the cells in the collection view.
+extern NSString* const kClearBrowsingHistoryCellAccessibilityIdentifier;
+extern NSString* const kClearCookiesCellAccessibilityIdentifier;
+extern NSString* const kClearCacheCellAccessibilityIdentifier;
+extern NSString* const kClearSavedPasswordsCellAccessibilityIdentifier;
+extern NSString* const kClearAutofillCellAccessibilityIdentifier;
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CLEAR_BROWSING_DATA_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.mm b/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.mm
new file mode 100644
index 0000000..4f1ad2c
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.mm
@@ -0,0 +1,24 @@
+// 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.
+
+#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// The accessibility identifier of the privacy settings collection view.
+NSString* const kClearBrowsingDataCollectionViewAccessibilityIdentifier =
+    @"kClearBrowsingDataCollectionViewAccessibilityIdentifier";
+// The accessibility identifiers of the cells in the collection view.
+NSString* const kClearBrowsingHistoryCellAccessibilityIdentifier =
+    @"kClearBrowsingHistoryCellAccessibilityIdentifier";
+NSString* const kClearCookiesCellAccessibilityIdentifier =
+    @"kClearCookiesCellAccessibilityIdentifier";
+NSString* const kClearCacheCellAccessibilityIdentifier =
+    @"kClearCacheCellAccessibilityIdentifier";
+NSString* const kClearSavedPasswordsCellAccessibilityIdentifier =
+    @"kClearSavedPasswordsCellAccessibilityIdentifier";
+NSString* const kClearAutofillCellAccessibilityIdentifier =
+    @"kClearAutofillCellAccessibilityIdentifier";
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h
index e5cb3586..29fb20f 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h
@@ -11,16 +11,6 @@
 class ChromeBrowserState;
 }  // namespace ios
 
-// The accessibility identifier of the privacy settings collection view.
-extern NSString* const kClearBrowsingDataCollectionViewId;
-
-// The accessibility identifiers of the cells in the collection view.
-extern NSString* const kClearBrowsingHistoryCellId;
-extern NSString* const kClearCookiesCellId;
-extern NSString* const kClearCacheCellId;
-extern NSString* const kClearSavedPasswordsCellId;
-extern NSString* const kClearAutofillCellId;
-
 // CollectionView for clearing browsing data (including history,
 // cookies, caches, passwords, and autofill).
 @interface ClearBrowsingDataCollectionViewController
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.mm b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.mm
index 7c6e073..dd8f72f 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.mm
@@ -51,6 +51,7 @@
 #import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
 #import "ios/chrome/browser/ui/commands/open_url_command.h"
 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
+#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
 #import "ios/chrome/browser/ui/settings/time_range_selector_collection_view_controller.h"
 #include "ios/chrome/browser/ui/ui_util.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
@@ -67,14 +68,6 @@
 #error "This file requires ARC support."
 #endif
 
-NSString* const kClearBrowsingDataCollectionViewId =
-    @"kClearBrowsingDataCollectionViewId";
-NSString* const kClearBrowsingHistoryCellId = @"kClearBrowsingHistoryCellId";
-NSString* const kClearCookiesCellId = @"kClearCookiesCellId";
-NSString* const kClearCacheCellId = @"kClearCacheCellId";
-NSString* const kClearSavedPasswordsCellId = @"kClearSavedPasswordsCellId";
-NSString* const kClearAutofillCellId = @"kClearAutofillCellId";
-
 namespace {
 
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
@@ -255,7 +248,7 @@
 
     self.title = l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_TITLE);
     self.collectionViewAccessibilityIdentifier =
-        kClearBrowsingDataCollectionViewId;
+        kClearBrowsingDataCollectionViewAccessibilityIdentifier;
 
     if (experimental_flags::IsNewClearBrowsingDataUIEnabled()) {
       constexpr int maxValue =
@@ -770,15 +763,15 @@
 - (NSString*)getAccessibilityIdentifierFromItemType:(NSInteger)itemType {
   switch (itemType) {
     case ItemTypeDataTypeBrowsingHistory:
-      return kClearBrowsingHistoryCellId;
+      return kClearBrowsingHistoryCellAccessibilityIdentifier;
     case ItemTypeDataTypeCookiesSiteData:
-      return kClearCookiesCellId;
+      return kClearCookiesCellAccessibilityIdentifier;
     case ItemTypeDataTypeCache:
-      return kClearCacheCellId;
+      return kClearCacheCellAccessibilityIdentifier;
     case ItemTypeDataTypeSavedPasswords:
-      return kClearSavedPasswordsCellId;
+      return kClearSavedPasswordsCellAccessibilityIdentifier;
     case ItemTypeDataTypeAutofill:
-      return kClearAutofillCellId;
+      return kClearAutofillCellAccessibilityIdentifier;
     default: {
       NOTREACHED();
       return nil;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index 806cfc77..e30da1d 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -108,7 +108,8 @@
   if (self = [super init]) {
     _regularTabsViewController = [[GridViewController alloc] init];
     _incognitoTabsViewController = [[GridViewController alloc] init];
-    _remoteTabsViewController = [[RecentTabsTableViewController alloc] init];
+    _remoteTabsViewController = [[RecentTabsTableViewController alloc]
+        initWithoutRecentlyClosedSection];
   }
   return self;
 }
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index 66b6eeb..951b667a 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -22,6 +22,7 @@
 #import "ios/chrome/browser/ui/payments/payment_request_view_controller.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #import "ios/chrome/browser/ui/settings/accounts_collection_view_controller.h"
+#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
 #import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller.h"
 #import "ios/chrome/browser/ui/settings/import_data_collection_view_controller.h"
@@ -258,7 +259,8 @@
 }
 
 id<GREYMatcher> ClearBrowsingDataCollectionView() {
-  return grey_accessibilityID(kClearBrowsingDataCollectionViewId);
+  return grey_accessibilityID(
+      kClearBrowsingDataCollectionViewAccessibilityIdentifier);
 }
 
 id<GREYMatcher> SettingsMenuButton() {
@@ -346,19 +348,19 @@
 }
 
 id<GREYMatcher> ClearBrowsingHistoryButton() {
-  return grey_accessibilityID(kClearBrowsingHistoryCellId);
+  return grey_accessibilityID(kClearBrowsingHistoryCellAccessibilityIdentifier);
 }
 
 id<GREYMatcher> ClearCookiesButton() {
-  return grey_accessibilityID(kClearCookiesCellId);
+  return grey_accessibilityID(kClearCookiesCellAccessibilityIdentifier);
 }
 
 id<GREYMatcher> ClearCacheButton() {
-  return grey_accessibilityID(kClearCacheCellId);
+  return grey_accessibilityID(kClearCacheCellAccessibilityIdentifier);
 }
 
 id<GREYMatcher> ClearSavedPasswordsButton() {
-  return grey_accessibilityID(kClearSavedPasswordsCellId);
+  return grey_accessibilityID(kClearSavedPasswordsCellAccessibilityIdentifier);
 }
 
 id<GREYMatcher> ContentSuggestionCollectionView() {
diff --git a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
index 29d0aae..c43e123 100644
--- a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
+++ b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
@@ -181,8 +181,12 @@
                 // will get notified via a CrPhotoSessionListener. Since |handler| is null, we'll
                 // work on the current Thread Looper.
                 session.capture(mPhotoRequest, null, null);
-            } catch (CameraAccessException e) {
-                Log.e(TAG, "capture() error");
+            } catch (CameraAccessException ex) {
+                Log.e(TAG, "capture() CameraAccessException", ex);
+                notifyTakePhotoError(mCallbackId);
+                return;
+            } catch (IllegalStateException ex) {
+                Log.e(TAG, "capture() IllegalStateException", ex);
                 notifyTakePhotoError(mCallbackId);
                 return;
             }
diff --git a/media/cast/sender/frame_sender.cc b/media/cast/sender/frame_sender.cc
index 1707182c..6cd4f67 100644
--- a/media/cast/sender/frame_sender.cc
+++ b/media/cast/sender/frame_sender.cc
@@ -20,12 +20,15 @@
 namespace cast {
 namespace {
 
-const int kMinSchedulingDelayMs = 1;
-const int kNumAggressiveReportsSentAtStart = 100;
+constexpr int kNumAggressiveReportsSentAtStart = 100;
+constexpr base::TimeDelta kMinSchedulingDelay =
+    base::TimeDelta::FromMilliseconds(1);
+constexpr base::TimeDelta kReceiverProcessTime =
+    base::TimeDelta::FromMilliseconds(250);
 
 // The additional number of frames that can be in-flight when input exceeds the
 // maximum frame rate.
-const int kMaxFrameBurst = 5;
+constexpr int kMaxFrameBurst = 5;
 
 }  // namespace
 
@@ -75,6 +78,7 @@
       picture_lost_at_receiver_(false),
       rtp_timebase_(config.rtp_timebase),
       is_audio_(config.rtp_payload_type <= RtpPayloadType::AUDIO_LAST),
+      max_ack_delay_(config.max_playout_delay),
       weak_factory_(this) {
   DCHECK(transport_sender_);
   DCHECK_GT(rtp_timebase_, 0);
@@ -106,8 +110,8 @@
 
   cast_environment_->PostDelayedTask(
       CastEnvironment::MAIN, FROM_HERE,
-      base::Bind(&FrameSender::SendRtcpReport, weak_factory_.GetWeakPtr(),
-                 true),
+      base::BindRepeating(&FrameSender::SendRtcpReport,
+                          weak_factory_.GetWeakPtr(), true),
       base::TimeDelta::FromMilliseconds(kRtcpReportIntervalMs));
 }
 
@@ -136,9 +140,12 @@
     ScheduleNextRtcpReport();
 }
 
-void FrameSender::OnMeasuredRoundTripTime(base::TimeDelta rtt) {
-  DCHECK(rtt > base::TimeDelta());
-  current_round_trip_time_ = rtt;
+void FrameSender::OnMeasuredRoundTripTime(base::TimeDelta round_trip_time) {
+  DCHECK_GT(round_trip_time, base::TimeDelta());
+  current_round_trip_time_ = round_trip_time;
+  max_ack_delay_ = 2 * std::max(current_round_trip_time_, base::TimeDelta()) +
+                   kReceiverProcessTime;
+  max_ack_delay_ = std::min(max_ack_delay_, target_playout_delay_);
 }
 
 void FrameSender::SetTargetPlayoutDelay(
@@ -155,6 +162,7 @@
           << target_playout_delay_.InMilliseconds() << " ms to "
           << new_target_playout_delay.InMilliseconds() << " ms.";
   target_playout_delay_ = new_target_playout_delay;
+  max_ack_delay_ = std::min(max_ack_delay_, target_playout_delay_);
   send_target_playout_delay_ = true;
   congestion_control_->UpdateTargetPlayoutDelay(target_playout_delay_);
 }
@@ -164,7 +172,7 @@
   DCHECK(!last_send_time_.is_null());
   const base::TimeDelta time_since_last_send =
       cast_environment_->Clock()->NowTicks() - last_send_time_;
-  if (time_since_last_send > target_playout_delay_) {
+  if (time_since_last_send > max_ack_delay_) {
     if (latest_acked_frame_id_ == last_sent_frame_id_) {
       // Last frame acked, no point in doing anything
     } else {
@@ -180,14 +188,12 @@
   DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
   DCHECK(!last_send_time_.is_null());
   base::TimeDelta time_to_next =
-      last_send_time_ - cast_environment_->Clock()->NowTicks() +
-      target_playout_delay_;
-  time_to_next = std::max(
-      time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs));
+      last_send_time_ - cast_environment_->Clock()->NowTicks() + max_ack_delay_;
+  time_to_next = std::max(time_to_next, kMinSchedulingDelay);
   cast_environment_->PostDelayedTask(
-      CastEnvironment::MAIN,
-      FROM_HERE,
-      base::Bind(&FrameSender::ResendCheck, weak_factory_.GetWeakPtr()),
+      CastEnvironment::MAIN, FROM_HERE,
+      base::BindRepeating(&FrameSender::ResendCheck,
+                          weak_factory_.GetWeakPtr()),
       time_to_next);
 }
 
@@ -253,6 +259,7 @@
       cancel_sending_frames.push_back(id);
     }
     transport_sender_->CancelSendingFrames(ssrc_, cancel_sending_frames);
+    OnCancelSendingFrames();
   }
 
   last_send_time_ = cast_environment_->Clock()->NowTicks();
@@ -325,6 +332,8 @@
   transport_sender_->InsertFrame(ssrc_, *encoded_frame);
 }
 
+void FrameSender::OnCancelSendingFrames() {}
+
 void FrameSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) {
   DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
 
@@ -418,6 +427,7 @@
           current_round_trip_time_.InMicroseconds());
     } while (latest_acked_frame_id_ < cast_feedback.ack_frame_id);
     transport_sender_->CancelSendingFrames(ssrc_, frames_to_cancel);
+    OnCancelSendingFrames();
   }
 }
 
diff --git a/media/cast/sender/frame_sender.h b/media/cast/sender/frame_sender.h
index 177193f0..c4a4ace 100644
--- a/media/cast/sender/frame_sender.h
+++ b/media/cast/sender/frame_sender.h
@@ -3,8 +3,6 @@
 // found in the LICENSE file.
 //
 // This is the base class for an object that send frames to a receiver.
-// TODO(hclam): Refactor such that there is no separate AudioSender vs.
-// VideoSender, and the functionality of both is rolled into this class.
 
 #ifndef MEDIA_CAST_SENDER_FRAME_SENDER_H_
 #define MEDIA_CAST_SENDER_FRAME_SENDER_H_
@@ -56,6 +54,9 @@
   // of sent, unacknowledged frames.
   virtual base::TimeDelta GetInFlightMediaDuration() const = 0;
 
+  // One or more frames were canceled.
+  virtual void OnCancelSendingFrames();
+
  protected:
   class RtcpClient : public RtcpObserver {
    public:
@@ -94,7 +95,7 @@
 
  protected:
   // Schedule and execute periodic checks for re-sending packets.  If no
-  // acknowledgements have been received for "too long," AudioSender will
+  // acknowledgements have been received for "too long," FrameSender will
   // speculatively re-send certain packets of an unacked frame to kick-start
   // re-transmission.  This is a last resort tactic to prevent the session from
   // getting stuck after a long outage.
@@ -164,7 +165,7 @@
   // Counts the number of duplicate ACK that are being received.  When this
   // number reaches a threshold, the sender will take this as a sign that the
   // receiver hasn't yet received the first packet of the next frame.  In this
-  // case, VideoSender will trigger a re-send of the next frame.
+  // case, FrameSender will trigger a re-send of the next frame.
   int duplicate_ack_counter_;
 
   // This object controls how we change the bitrate to make sure the
@@ -188,6 +189,10 @@
 
   const bool is_audio_;
 
+  // This is the maximum delay that the sender should get ack from receiver.
+  // Otherwise, sender will call ResendForKickstart().
+  base::TimeDelta max_ack_delay_;
+
   // Ring buffers to keep track of recent frame timestamps (both in terms of
   // local reference time and RTP media time).  These should only be accessed
   // through the Record/GetXXX() methods.  The index into this ring
diff --git a/net/base/network_interfaces_fuchsia.cc b/net/base/network_interfaces_fuchsia.cc
index 4c4c20b1..1c03e968 100644
--- a/net/base/network_interfaces_fuchsia.cc
+++ b/net/base/network_interfaces_fuchsia.cc
@@ -4,14 +4,14 @@
 
 #include "net/base/network_interfaces.h"
 
-#include <netstack/cpp/fidl.h>
+#include <fuchsia/netstack/cpp/fidl.h>
 
 #include "base/fuchsia/component_context.h"
 #include "net/base/ip_endpoint.h"
 
 namespace net {
 
-IPAddress NetAddressToIPAddress(const netstack::NetAddress& addr) {
+IPAddress NetAddressToIPAddress(const fuchsia::netstack::NetAddress& addr) {
   if (addr.ipv4) {
     return IPAddress(addr.ipv4->addr.data(), addr.ipv4->addr.count());
   }
@@ -22,25 +22,25 @@
 }
 
 bool GetNetworkList(NetworkInterfaceList* networks, int policy) {
-  netstack::NetstackSyncPtr netstack =
+  fuchsia::netstack::NetstackSyncPtr netstack =
       base::fuchsia::ComponentContext::GetDefault()
-          ->ConnectToServiceSync<netstack::Netstack>();
+          ->ConnectToServiceSync<fuchsia::netstack::Netstack>();
 
-  fidl::VectorPtr<netstack::NetInterface> interfaces;
+  fidl::VectorPtr<fuchsia::netstack::NetInterface> interfaces;
   if (!netstack->GetInterfaces(&interfaces))
     return false;
 
   for (auto& interface : interfaces.get()) {
     // Check if the interface is up.
-    if (!(interface.flags & netstack::NetInterfaceFlagUp))
+    if (!(interface.flags & fuchsia::netstack::NetInterfaceFlagUp))
       continue;
 
     // Skip loopback.
-    if (interface.features & netstack::interfaceFeatureLoopback)
+    if (interface.features & fuchsia::netstack::interfaceFeatureLoopback)
       continue;
 
     NetworkChangeNotifier::ConnectionType connection_type =
-        (interface.features & netstack::interfaceFeatureWlan)
+        (interface.features & fuchsia::netstack::interfaceFeatureWlan)
             ? NetworkChangeNotifier::CONNECTION_WIFI
             : NetworkChangeNotifier::CONNECTION_UNKNOWN;
 
diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc
index 7d27fbd..e436bdd 100644
--- a/remoting/client/plugin/chromoting_instance.cc
+++ b/remoting/client/plugin/chromoting_instance.cc
@@ -7,6 +7,7 @@
 #include <nacl_io/nacl_io.h>
 #include <sys/mount.h>
 
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
@@ -58,6 +59,7 @@
 #include "remoting/signaling/delegating_signal_strategy.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
 #include "third_party/webrtc/rtc_base/helpers.h"
+#include "third_party/webrtc/rtc_base/network.h"
 #include "url/gurl.h"
 
 namespace remoting {
@@ -689,14 +691,14 @@
                  weak_factory_.GetWeakPtr())));
 
   // Create TransportContext.
-  scoped_refptr<protocol::TransportContext> transport_context(
-      new protocol::TransportContext(
-          signal_strategy_.get(),
-          std::make_unique<PepperPortAllocatorFactory>(this),
-          std::make_unique<PepperUrlRequestFactory>(this),
-          protocol::NetworkSettings(
-              protocol::NetworkSettings::NAT_TRAVERSAL_FULL),
-          protocol::TransportRole::CLIENT));
+  transport_context_ = new protocol::TransportContext(
+      signal_strategy_.get(),
+      std::make_unique<PepperPortAllocatorFactory>(
+          this, base::BindRepeating(&ChromotingInstance::SendNetworkInfo,
+                                    base::Unretained(this))),
+      std::make_unique<PepperUrlRequestFactory>(this),
+      protocol::NetworkSettings(protocol::NetworkSettings::NAT_TRAVERSAL_FULL),
+      protocol::TransportRole::CLIENT);
 
   std::unique_ptr<protocol::CandidateSessionConfig> config =
       protocol::CandidateSessionConfig::CreateDefault();
@@ -711,7 +713,7 @@
   client_->set_protocol_config(std::move(config));
 
   // Kick off the connection.
-  client_->Start(signal_strategy_.get(), client_auth_config, transport_context,
+  client_->Start(signal_strategy_.get(), client_auth_config, transport_context_,
                  host_jid, capabilities);
 
   // Connect the input pipeline to the protocol stub.
@@ -1011,6 +1013,7 @@
   video_renderer_.reset();
   audio_player_.reset();
   stats_update_timer_.Stop();
+  transport_context_ = nullptr;
 }
 
 void ChromotingInstance::PostChromotingMessage(const std::string& method,
@@ -1161,4 +1164,24 @@
   }
 }
 
+void ChromotingInstance::SendNetworkInfo() {
+  if (!transport_context_)
+    return;
+  rtc::NetworkManager* network_manager = transport_context_->network_manager();
+  if (!network_manager)
+    return;
+
+  rtc::NetworkManager::NetworkList network_list;
+  network_manager->GetNetworks(&network_list);
+
+  std::set<std::string> network_names;
+  for (const auto* network : network_list) {
+    network_names.insert(network->name());
+  }
+  pp::VarDictionary dictionary;
+  dictionary.Set(pp::Var("interfaceCount"),
+                 static_cast<int32_t>(network_names.size()));
+  PostChromotingMessage("networkInfo", dictionary);
+}
+
 }  // namespace remoting
diff --git a/remoting/client/plugin/chromoting_instance.h b/remoting/client/plugin/chromoting_instance.h
index 1b0def43..66cd2ab 100644
--- a/remoting/client/plugin/chromoting_instance.h
+++ b/remoting/client/plugin/chromoting_instance.h
@@ -242,6 +242,8 @@
       bool pairing_supported,
       const protocol::SecretFetchedCallback& secret_fetched_callback);
 
+  void SendNetworkInfo();
+
   bool initialized_;
 
   scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner_;
@@ -260,6 +262,8 @@
 
   std::unique_ptr<ChromotingClient> client_;
 
+  scoped_refptr<protocol::TransportContext> transport_context_;
+
   // Input pipeline components, in reverse order of distance from input source.
   protocol::MouseInputFilter mouse_input_filter_;
   TouchInputScaler touch_input_scaler_;
diff --git a/remoting/client/plugin/pepper_network_manager.cc b/remoting/client/plugin/pepper_network_manager.cc
index fb38f49..a52aedc5 100644
--- a/remoting/client/plugin/pepper_network_manager.cc
+++ b/remoting/client/plugin/pepper_network_manager.cc
@@ -96,11 +96,14 @@
   bool changed = false;
   MergeNetworkList(networks, &changed);
   if (changed)
-    SignalNetworksChanged();
+    SendNetworksChangedSignal();
 }
 
 void PepperNetworkManager::SendNetworksChangedSignal() {
   SignalNetworksChanged();
+  if (network_info_callback_) {
+    network_info_callback_.Run();
+  }
 }
 
 }  // namespace remoting
diff --git a/remoting/client/plugin/pepper_network_manager.h b/remoting/client/plugin/pepper_network_manager.h
index 43d27ef..5803171 100644
--- a/remoting/client/plugin/pepper_network_manager.h
+++ b/remoting/client/plugin/pepper_network_manager.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/memory/weak_ptr.h"
 #include "ppapi/cpp/instance_handle.h"
@@ -25,6 +26,8 @@
 // monitor the host system's network interfaces.
 class PepperNetworkManager : public rtc::NetworkManagerBase {
  public:
+  typedef base::RepeatingCallback<void()> NetworkInfoCallback;
+
   PepperNetworkManager(const pp::InstanceHandle& instance);
   ~PepperNetworkManager() override;
 
@@ -32,6 +35,10 @@
   void StartUpdating() override;
   void StopUpdating() override;
 
+  void set_network_info_callback(NetworkInfoCallback callback) {
+    network_info_callback_ = callback;
+  }
+
  private:
   static void OnNetworkListCallbackHandler(void* user_data,
                                            PP_Resource list_resource);
@@ -43,6 +50,7 @@
   pp::NetworkMonitor monitor_;
   int start_count_;
   bool network_list_received_;
+  NetworkInfoCallback network_info_callback_;
 
   pp::CompletionCallbackFactory<PepperNetworkManager> callback_factory_;
 
diff --git a/remoting/client/plugin/pepper_port_allocator_factory.cc b/remoting/client/plugin/pepper_port_allocator_factory.cc
index f71b730..3730681 100644
--- a/remoting/client/plugin/pepper_port_allocator_factory.cc
+++ b/remoting/client/plugin/pepper_port_allocator_factory.cc
@@ -4,7 +4,9 @@
 
 #include "remoting/client/plugin/pepper_port_allocator_factory.h"
 
-#include "base/memory/ptr_util.h"
+#include <memory>
+#include <utility>
+
 #include "remoting/client/plugin/pepper_network_manager.h"
 #include "remoting/client/plugin/pepper_packet_socket_factory.h"
 #include "remoting/protocol/port_allocator.h"
@@ -13,17 +15,27 @@
 namespace remoting {
 
 PepperPortAllocatorFactory::PepperPortAllocatorFactory(
-    pp::InstanceHandle pp_instance)
-    : pp_instance_(pp_instance) {}
+    pp::InstanceHandle pp_instance,
+    PepperNetworkManager::NetworkInfoCallback callback)
+    : pp_instance_(pp_instance), network_info_callback_(callback) {}
 
 PepperPortAllocatorFactory::~PepperPortAllocatorFactory() {}
 
 std::unique_ptr<cricket::PortAllocator>
 PepperPortAllocatorFactory::CreatePortAllocator(
     scoped_refptr<protocol::TransportContext> transport_context) {
+  auto network_manager = std::make_unique<PepperNetworkManager>(pp_instance_);
+  if (!transport_context->network_manager()) {
+    // Only store (and notify network changes from) the first NetworkManager
+    // instance.
+    // TODO(crbug.com/848045): Remove this when NetworkManager becomes a
+    // singleton.
+    transport_context->set_network_manager(network_manager.get());
+    network_manager->set_network_info_callback(network_info_callback_);
+  }
   return std::make_unique<protocol::PortAllocator>(
-      base::WrapUnique(new PepperNetworkManager(pp_instance_)),
-      base::WrapUnique(new PepperPacketSocketFactory(pp_instance_)),
+      std::move(network_manager),
+      std::make_unique<PepperPacketSocketFactory>(pp_instance_),
       transport_context);
 }
 
diff --git a/remoting/client/plugin/pepper_port_allocator_factory.h b/remoting/client/plugin/pepper_port_allocator_factory.h
index eb27d8ea..2c15f21 100644
--- a/remoting/client/plugin/pepper_port_allocator_factory.h
+++ b/remoting/client/plugin/pepper_port_allocator_factory.h
@@ -10,13 +10,16 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "ppapi/cpp/instance_handle.h"
+#include "remoting/client/plugin/pepper_network_manager.h"
 #include "remoting/protocol/port_allocator_factory.h"
 
 namespace remoting {
 
 class PepperPortAllocatorFactory : public protocol::PortAllocatorFactory {
  public:
-  PepperPortAllocatorFactory(pp::InstanceHandle pp_instance);
+  PepperPortAllocatorFactory(
+      pp::InstanceHandle pp_instance,
+      PepperNetworkManager::NetworkInfoCallback callback);
   ~PepperPortAllocatorFactory() override;
 
    // PortAllocatorFactory interface.
@@ -25,6 +28,7 @@
 
  private:
   pp::InstanceHandle pp_instance_;
+  PepperNetworkManager::NetworkInfoCallback network_info_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(PepperPortAllocatorFactory);
 };
diff --git a/remoting/protocol/transport_context.h b/remoting/protocol/transport_context.h
index 445eef94..4df7a678 100644
--- a/remoting/protocol/transport_context.h
+++ b/remoting/protocol/transport_context.h
@@ -17,6 +17,10 @@
 #include "remoting/protocol/network_settings.h"
 #include "remoting/protocol/transport.h"
 
+namespace rtc {
+class NetworkManager;
+}  // namespace rtc
+
 namespace remoting {
 
 class OAuthTokenGetter;
@@ -70,6 +74,15 @@
   // previous GetIceConfig() requests.
   void set_relay_mode(RelayMode relay_mode) { relay_mode_ = relay_mode; }
 
+  // Sets a reference to the NetworkManager that holds the list of
+  // network interfaces. If the NetworkManager is deleted while this
+  // TransportContext is live, the caller should set this to nullptr.
+  // TODO(crbug.com/848045): This should be a singleton - either a global
+  // instance, or one that is owned by this TransportContext.
+  void set_network_manager(rtc::NetworkManager* network_manager) {
+    network_manager_ = network_manager;
+  }
+
   // Prepares fresh JingleInfo. It may be called while connection is being
   // negotiated to minimize the chance that the following GetIceConfig() will
   // be blocking.
@@ -86,6 +99,7 @@
   }
   const NetworkSettings& network_settings() const { return network_settings_; }
   TransportRole role() const { return role_; }
+  rtc::NetworkManager* network_manager() const { return network_manager_; }
 
  private:
   friend class base::RefCountedThreadSafe<TransportContext>;
@@ -106,6 +120,8 @@
 
   RelayMode relay_mode_ = RelayMode::GTURN;
 
+  rtc::NetworkManager* network_manager_ = nullptr;
+
   std::array<std::unique_ptr<IceConfigRequest>, kNumRelayModes>
       ice_config_request_;
   std::array<IceConfig, kNumRelayModes> ice_config_;
diff --git a/remoting/webapp/base/js/chromoting_event.js b/remoting/webapp/base/js/chromoting_event.js
index 2d8d3d03..abab8cd 100644
--- a/remoting/webapp/base/js/chromoting_event.js
+++ b/remoting/webapp/base/js/chromoting_event.js
@@ -130,6 +130,8 @@
   this.client_fullscreen;
   /** @type {remoting.ChromotingEvent.ChromotingDotComMigration} */
   this.chromoting_dot_com_migration;
+  /** @type {number} */
+  this.number_of_network_interfaces;
 
   this.init_();
 };
diff --git a/remoting/webapp/base/js/client_plugin_impl.js b/remoting/webapp/base/js/client_plugin_impl.js
index cba6b268..8e0348c 100644
--- a/remoting/webapp/base/js/client_plugin_impl.js
+++ b/remoting/webapp/base/js/client_plugin_impl.js
@@ -51,7 +51,7 @@
   /** @private {Array<string>} */
   this.capabilities_ = capabilities;
 
-  /** @private {remoting.ClientPlugin.ConnectionEventHandler} */
+  /** @private {remoting.ClientSession} */
   this.connectionEventHandler_ = null;
 
   /** @private {?function(string, number, number)} */
@@ -265,6 +265,9 @@
     } else if (message.method == 'onFirstFrameReceived') {
       handler.onFirstFrameReceived();
 
+    } else if (message.method == 'networkInfo') {
+      handler.getLogger().setNetworkInterfaceCount(
+          base.getNumberAttr(message.data, 'interfaceCount'));
     }
   }
 
diff --git a/remoting/webapp/base/js/session_logger.js b/remoting/webapp/base/js/session_logger.js
index e2a34561..66d0c4f49 100644
--- a/remoting/webapp/base/js/session_logger.js
+++ b/remoting/webapp/base/js/session_logger.js
@@ -68,6 +68,9 @@
   /** @private {remoting.ChromotingEvent.FeatureTracker} */
   this.featureTracker_ = null;
 
+  /** @private {number} */
+  this.networkInterfaceCount_;
+
   this.setSessionId_();
 };
 
@@ -170,6 +173,13 @@
 };
 
 /**
+ * @param {number} count
+ */
+remoting.SessionLogger.prototype.setNetworkInterfaceCount = function(count) {
+  this.networkInterfaceCount_ = count;
+};
+
+/**
  * @return {string} The current session id. This is random GUID, refreshed
  *     every 24hrs.
  */
@@ -402,6 +412,9 @@
   entry.host_version = this.hostVersion_;
   entry.host_os = this.hostOs_;
   entry.host_os_version = this.hostOsVersion_;
+  if (this.networkInterfaceCount_ != undefined) {
+    entry.number_of_network_interfaces = this.networkInterfaceCount_;
+  }
 };
 
 /**
diff --git a/remoting/webapp/base/js/telemetry_event_writer.js b/remoting/webapp/base/js/telemetry_event_writer.js
index 75291f8..b2de2561 100644
--- a/remoting/webapp/base/js/telemetry_event_writer.js
+++ b/remoting/webapp/base/js/telemetry_event_writer.js
@@ -75,7 +75,15 @@
 remoting.TelemetryEventWriter.Service.prototype.write =
     function(windowId, event) {
   this.sessionMonitor_.trackSessionStateChanges(windowId, event);
-  this.eventWriter_.write(event);
+      remoting.WebsiteUsageTracker.getVisitCount().then(
+          (count) => {
+            // This field is only available asynchronously, which makes it
+            // difficult to add as part of the ChromotingEvent class in the
+            // app page; adding it here is much simper, as background page
+            // telemetry is already asynchronous.
+            event['website_and_app_user'] = count > 0;
+            this.eventWriter_.write(event);
+          });
 };
 
 /**
diff --git a/remoting/webapp/base/js/telemetry_event_writer_unittest.js b/remoting/webapp/base/js/telemetry_event_writer_unittest.js
index ad05602..bf50e6b 100644
--- a/remoting/webapp/base/js/telemetry_event_writer_unittest.js
+++ b/remoting/webapp/base/js/telemetry_event_writer_unittest.js
@@ -20,11 +20,25 @@
  *  @param {number} index
  *  @param {Object} expected
  */
-function verifyEvent(stub, assert, index, expected) {
+function verifyEventAtIndex(stub, assert, index, expected) {
   var event = /** @type {Object} */ (stub.getCall(index).args[0]);
   for (var key in expected) {
-    assert.equal(event[key], expected[key], 'Verifying ChromotingEvent.' + key);
+    if (event[key] != expected[key]) {
+      return false;
+    }
   }
+  return true;
+}
+
+function verifyEvent(stub, assert, expected) {
+  var match = false;
+  for (var index = 0; index < stub.callCount; ++ index) {
+    if (verifyEventAtIndex(stub, assert, index, expected)) {
+      match = true;
+      break;
+    }
+  }
+  assert.ok(match, 'No logged event matches expected.');
 }
 
 QUnit.module('TelemetryEventWriter', {
@@ -44,6 +58,12 @@
     fakeHost.hostVersion = 'host_version';
     logger.setHost(fakeHost);
     logger.setConnectionType('stun');
+    chrome.storage = {
+      sync: {
+        get: sinon.stub(),
+      }
+    };
+    chrome.storage.sync.get.callsArgWith(1, 0);
   },
   afterEach: function() {
     base.dispose(service);
@@ -56,7 +76,10 @@
 QUnit.test('Client.write() should write request.', function(assert){
   var mockEventWriter = sinon.mock(eventWriter);
   return service.init().then(function(){
-    mockEventWriter.expects('write').once().withArgs({id: '1'});
+    mockEventWriter.expects('write').once().withArgs({
+      id: '1',
+      website_and_app_user: false
+    });
     return remoting.TelemetryEventWriter.Client.write({id: '1'});
   }).then(function(){
     mockEventWriter.verify();
@@ -99,7 +122,7 @@
     return service.unbindSession('fake-window-id');
   }).then(function() {
     var Event = remoting.ChromotingEvent;
-    verifyEvent(writeStub, assert, 1, {
+    verifyEvent(writeStub, assert, {
       type: Event.Type.SESSION_STATE,
       session_state: Event.SessionState.CONNECTION_CANCELED,
       connection_error: Event.ConnectionError.NONE,
@@ -126,7 +149,7 @@
     return service.unbindSession('fake-window-id');
   }).then(function() {
     var Event = remoting.ChromotingEvent;
-    verifyEvent(writeStub, assert, 1, {
+    verifyEvent(writeStub, assert, {
       type: Event.Type.SESSION_STATE,
       session_state: Event.SessionState.CONNECTION_CANCELED,
       connection_error: Event.ConnectionError.NONE,
@@ -157,7 +180,7 @@
     return service.unbindSession('fake-window-id');
   }).then(function() {
     var Event = remoting.ChromotingEvent;
-    verifyEvent(writeStub, assert, 2, {
+    verifyEvent(writeStub, assert, {
       type: Event.Type.SESSION_STATE,
       session_state: Event.SessionState.CLOSED,
       connection_error: Event.ConnectionError.NONE,
diff --git a/remoting/webapp/base/js/website_usage_tracker.js b/remoting/webapp/base/js/website_usage_tracker.js
new file mode 100644
index 0000000..762b6be
--- /dev/null
+++ b/remoting/webapp/base/js/website_usage_tracker.js
@@ -0,0 +1,45 @@
+// 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.
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+(function(){
+
+'use strict';
+
+/**
+ * @constructor
+ */
+remoting.WebsiteUsageTracker = function() {
+  base.Ipc.getInstance().register(
+      'websiteConnectionAttempted',
+      remoting.WebsiteUsageTracker.onWebsiteVisited_,
+      true);
+};
+
+remoting.WebsiteUsageTracker.getVisitCount = function() {
+  var result = new base.Deferred();
+  chrome.storage.sync.get(
+      [STORAGE_KEY],
+      (results) => {
+        var count = Number(results[STORAGE_KEY]);
+        result.resolve(isNaN(count) ? 0 : count);
+      });
+  return result.promise();
+};
+
+remoting.WebsiteUsageTracker.onWebsiteVisited_ = function() {
+  remoting.WebsiteUsageTracker.getVisitCount().then(
+      (count) => {
+        chrome.storage.sync.set({
+          [STORAGE_KEY]: count + 1
+        });
+      });
+};
+
+
+var STORAGE_KEY = 'website-host-connection-attempts-count';
+
+}());
diff --git a/remoting/webapp/crd/js/background.js b/remoting/webapp/crd/js/background.js
index ec1f9c2..0be34f9 100644
--- a/remoting/webapp/crd/js/background.js
+++ b/remoting/webapp/crd/js/background.js
@@ -21,6 +21,8 @@
   this.telemetryService_ = null;
   /** @private {remoting.OptionsExporter} */
   this.optionsExporter_ = null;
+  /** @private {remoting.WebsiteUsageTracker} */
+  this.websiteUsageTracker_ = null;
   /** @private */
   this.disposables_ = new base.Disposables();
   this.preInit_();
@@ -52,6 +54,7 @@
         this.activationHandler_, remoting.ActivationHandler.Events.windowClosed,
         this.telemetryService_.unbindSession.bind(this.telemetryService_)));
     this.optionsExporter_ = new remoting.OptionsExporter();
+    this.websiteUsageTracker_ = new remoting.WebsiteUsageTracker();
   } else {
     this.appLauncher_ = new remoting.V1AppLauncher();
   }
diff --git a/remoting/webapp/files.gni b/remoting/webapp/files.gni
index 2dfd7428..786d3dcd 100644
--- a/remoting/webapp/files.gni
+++ b/remoting/webapp/files.gni
@@ -420,6 +420,7 @@
 # These JS files are specific to the background page and are not part of
 # the main JS files.
 remoting_webapp_background_html_js_files = [
+  "base/js/website_usage_tracker.js",
   "crd/js/activation_handler.js",
   "crd/js/app_launcher.js",
   "crd/js/background.js",
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index 4db014a9..3d84720 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -203,6 +203,14 @@
       "proxy_resolver_factory_mojo_unittest.cc",
       "websocket_throttler_unittest.cc",
     ]
+
+    data = [
+      "//net/tools/testserver/",
+      "//services/test/data",
+      "//third_party/pyftpdlib/",
+      "//third_party/pywebsocket/",
+      "//third_party/tlslite/",
+    ]
   }
 
   deps = [
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index f5f4338..d983cc3 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -87,6 +87,16 @@
 #include "net/reporting/reporting_service.h"
 #endif  // BUILDFLAG(ENABLE_REPORTING)
 
+#if defined(USE_NSS_CERTS)
+#include "net/cert_net/nss_ocsp.h"
+#endif  // defined(USE_NSS_CERTS)
+
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
+#include "net/cert/cert_net_fetcher.h"
+#include "net/cert_net/cert_net_fetcher_impl.h"
+#endif
+
 namespace network {
 
 namespace {
@@ -287,6 +297,17 @@
   if (network_service_)
     network_service_->DeregisterNetworkContext(this);
 
+  if (UseToValidateCerts()) {
+#if defined(USE_NSS_CERTS)
+    net::SetURLRequestContextForNSSHttpIO(nullptr);
+#endif
+
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
+    net::ShutdownGlobalCertNetFetcher();
+#endif
+  }
+
   if (url_request_context_ &&
       url_request_context_->transport_security_state()) {
     if (certificate_report_sender_) {
@@ -320,6 +341,10 @@
   g_cert_verifier_for_testing = cert_verifier;
 }
 
+bool NetworkContext::UseToValidateCerts() const {
+  return params_ && params_->use_to_validate_certs;
+}
+
 void NetworkContext::CreateURLLoaderFactory(
     mojom::URLLoaderFactoryRequest request,
     network::mojom::URLLoaderFactoryParamsPtr params,
@@ -865,6 +890,19 @@
         ->SetRequireCTDelegate(out_require_ct_delegate->get());
   }
 
+  // These must be matched by cleanup code just before the URLRequestContext is
+  // destroyed.
+  if (network_context_params->use_to_validate_certs) {
+#if defined(USE_NSS_CERTS)
+    net::SetURLRequestContextForNSSHttpIO(result.url_request_context.get());
+#endif
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
+    net::SetGlobalCertNetFetcher(
+        net::CreateCertNetFetcher(result.url_request_context.get()));
+#endif
+  }
+
   return result;
 }
 
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 9344f0fa..cafbeb3 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -101,6 +101,13 @@
   // Sets a global CertVerifier to use when initializing all profiles.
   static void SetCertVerifierForTesting(net::CertVerifier* cert_verifier);
 
+  // Whether the NetworkContext should be used by all NetworkContexts to
+  // validate certs on some platforms.  May only be set to true the first
+  // NetworkContext created using the NetworkService.  Destroying the
+  // NetworkContext with this set to true will destroy all other
+  // NetworkContexts.
+  bool UseToValidateCerts() const;
+
   net::URLRequestContext* url_request_context() { return url_request_context_; }
 
   NetworkService* network_service() { return network_service_; }
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 4fe7beda..4224c1d8 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -159,7 +159,7 @@
 
 NetworkService::~NetworkService() {
   // Destroy owned network contexts.
-  owned_network_contexts_.clear();
+  DestroyNetworkContexts();
 
   // All NetworkContexts (Owned and unowned) must have been deleted by this
   // point.
@@ -197,6 +197,10 @@
 }
 
 void NetworkService::RegisterNetworkContext(NetworkContext* network_context) {
+  // If UseToValidateCerts() is true, there must be no other NetworkContexts
+  // created yet.
+  DCHECK(!network_context->UseToValidateCerts() || network_contexts_.empty());
+
   DCHECK_EQ(0u, network_contexts_.count(network_context));
   network_contexts_.insert(network_context);
   if (quic_disabled_)
@@ -204,6 +208,11 @@
 }
 
 void NetworkService::DeregisterNetworkContext(NetworkContext* network_context) {
+  // If the NetworkContext is being used to validate certs, all other
+  // NetworkContexts must already have been destroyed.
+  DCHECK(!network_context->UseToValidateCerts() ||
+         network_contexts_.size() == 1);
+
   DCHECK_EQ(1u, network_contexts_.count(network_context));
   network_contexts_.erase(network_context);
 }
@@ -223,6 +232,10 @@
 void NetworkService::CreateNetworkContext(
     mojom::NetworkContextRequest request,
     mojom::NetworkContextParamsPtr params) {
+  // Only the first created NetworkContext can have |use_to_validate_certs| set
+  // to true.
+  DCHECK(!params->use_to_validate_certs || network_contexts_.empty());
+
   owned_network_contexts_.emplace(std::make_unique<NetworkContext>(
       this, std::move(request), std::move(params),
       base::BindOnce(&NetworkService::OnNetworkContextConnectionClosed,
@@ -282,8 +295,28 @@
   registry_->BindInterface(interface_name, std::move(interface_pipe));
 }
 
+void NetworkService::DestroyNetworkContexts() {
+  // Delete NetworkContexts. If there's a NetworkContext that's being used to
+  // validate certs, it must be deleted after all other NetworkContexts, to
+  // avoid use-after-frees.
+  for (auto it = owned_network_contexts_.begin();
+       it != owned_network_contexts_.end();) {
+    const auto last = it;
+    ++it;
+    if (!(*last)->UseToValidateCerts())
+      owned_network_contexts_.erase(last);
+  }
+  DCHECK_LE(owned_network_contexts_.size(), 1u);
+  owned_network_contexts_.clear();
+}
+
 void NetworkService::OnNetworkContextConnectionClosed(
     NetworkContext* network_context) {
+  if (network_context->UseToValidateCerts()) {
+    DestroyNetworkContexts();
+    return;
+  }
+
   auto it = owned_network_contexts_.find(network_context);
   DCHECK(it != owned_network_contexts_.end());
   owned_network_contexts_.erase(it);
diff --git a/services/network/network_service.h b/services/network/network_service.h
index 2f066fc..4842a88d 100644
--- a/services/network/network_service.h
+++ b/services/network/network_service.h
@@ -143,6 +143,8 @@
                        const std::string& interface_name,
                        mojo::ScopedMessagePipeHandle interface_pipe) override;
 
+  void DestroyNetworkContexts();
+
   // Called by a NetworkContext when its mojo pipe is closed. Deletes the
   // context.
   void OnNetworkContextConnectionClosed(NetworkContext* network_context);
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index b6143a4..c721690e 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -13,6 +13,7 @@
 #include "net/base/mock_network_change_notifier.h"
 #include "net/proxy_resolution/proxy_config.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/network_context.h"
 #include "services/network/network_service.h"
@@ -30,6 +31,9 @@
 
 const char kNetworkServiceName[] = "network";
 
+const base::FilePath::CharType kServicesTestData[] =
+    FILE_PATH_LITERAL("services/test/data");
+
 mojom::NetworkContextParamsPtr CreateContextParams() {
   mojom::NetworkContextParamsPtr params = mojom::NetworkContextParams::New();
   // Use a fixed proxy config, to avoid dependencies on local network
@@ -145,16 +149,25 @@
                     base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
   ~NetworkServiceTestWithService() override {}
 
-  void LoadURL(const GURL& url) {
+  void CreateNetworkContext() {
+    mojom::NetworkContextParamsPtr context_params =
+        mojom::NetworkContextParams::New();
+    network_service_->CreateNetworkContext(mojo::MakeRequest(&network_context_),
+                                           std::move(context_params));
+  }
+
+  void LoadURL(const GURL& url, int options = mojom::kURLLoadOptionNone) {
     ResourceRequest request;
     request.url = url;
     request.method = "GET";
     request.request_initiator = url::Origin();
-    StartLoadingURL(request, 0);
+    StartLoadingURL(request, 0 /* process_id */, options);
     client_->RunUntilComplete();
   }
 
-  void StartLoadingURL(const ResourceRequest& request, uint32_t process_id) {
+  void StartLoadingURL(const ResourceRequest& request,
+                       uint32_t process_id,
+                       int options = mojom::kURLLoadOptionNone) {
     client_.reset(new TestURLLoaderClient());
     mojom::URLLoaderFactoryPtr loader_factory;
     mojom::URLLoaderFactoryParamsPtr params =
@@ -165,7 +178,7 @@
                                              std::move(params));
 
     loader_factory->CreateLoaderAndStart(
-        mojo::MakeRequest(&loader_), 1, 1, mojom::kURLLoadOptionNone, request,
+        mojo::MakeRequest(&loader_), 1, 1, options, request,
         client_->CreateInterfacePtr(),
         net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
   }
@@ -176,21 +189,16 @@
   mojom::NetworkService* service() { return network_service_.get(); }
   mojom::NetworkContext* context() { return network_context_.get(); }
 
- private:
+ protected:
   std::unique_ptr<service_manager::Service> CreateService() override {
     return std::make_unique<ServiceTestClient>(this);
   }
 
   void SetUp() override {
-    base::FilePath services_test_data(FILE_PATH_LITERAL("services/test/data"));
-    test_server_.AddDefaultHandlers(services_test_data);
+    test_server_.AddDefaultHandlers(base::FilePath(kServicesTestData));
     ASSERT_TRUE(test_server_.Start());
     service_manager::test::ServiceTest::SetUp();
     connector()->BindInterface(kNetworkServiceName, &network_service_);
-    mojom::NetworkContextParamsPtr context_params =
-        mojom::NetworkContextParams::New();
-    network_service_->CreateNetworkContext(mojo::MakeRequest(&network_context_),
-                                           std::move(context_params));
   }
 
   net::EmbeddedTestServer test_server_;
@@ -205,12 +213,14 @@
 // Verifies that loading a URL through the network service's mojo interface
 // works.
 TEST_F(NetworkServiceTestWithService, Basic) {
+  CreateNetworkContext();
   LoadURL(test_server()->GetURL("/echo"));
   EXPECT_EQ(net::OK, client()->completion_status().error_code);
 }
 
 // Verifies that raw headers are only reported if requested.
 TEST_F(NetworkServiceTestWithService, RawRequestHeadersAbsent) {
+  CreateNetworkContext();
   ResourceRequest request;
   request.url = test_server()->GetURL("/server-redirect?/echo");
   request.method = "GET";
@@ -225,6 +235,7 @@
 }
 
 TEST_F(NetworkServiceTestWithService, RawRequestHeadersPresent) {
+  CreateNetworkContext();
   ResourceRequest request;
   request.url = test_server()->GetURL("/server-redirect?/echo");
   request.method = "GET";
@@ -268,6 +279,7 @@
 
 TEST_F(NetworkServiceTestWithService, RawRequestAccessControl) {
   const uint32_t process_id = 42;
+  CreateNetworkContext();
   ResourceRequest request;
   request.url = test_server()->GetURL("/nocache.html");
   request.method = "GET";
@@ -295,6 +307,7 @@
 }
 
 TEST_F(NetworkServiceTestWithService, SetNetworkConditions) {
+  CreateNetworkContext();
   mojom::NetworkConditionsPtr network_conditions =
       mojom::NetworkConditions::New();
   network_conditions->offline = true;
@@ -338,6 +351,118 @@
   EXPECT_EQ(net::OK, client()->completion_status().error_code);
 }
 
+// The SpawnedTestServer does not work on iOS.
+#if !defined(OS_IOS)
+
+class AllowBadCertsNetworkServiceClient : public mojom::NetworkServiceClient {
+ public:
+  explicit AllowBadCertsNetworkServiceClient(
+      network::mojom::NetworkServiceClientRequest
+          network_service_client_request)
+      : binding_(this, std::move(network_service_client_request)) {}
+  ~AllowBadCertsNetworkServiceClient() override {}
+
+  // network::mojom::NetworkServiceClient implementation:
+  void OnAuthRequired(uint32_t process_id,
+                      uint32_t routing_id,
+                      uint32_t request_id,
+                      const GURL& url,
+                      const GURL& site_for_cookies,
+                      bool first_auth_attempt,
+                      const scoped_refptr<net::AuthChallengeInfo>& auth_info,
+                      int32_t resource_type,
+                      network::mojom::AuthChallengeResponderPtr
+                          auth_challenge_responder) override {
+    NOTREACHED();
+  }
+
+  void OnCertificateRequested(
+      uint32_t process_id,
+      uint32_t routing_id,
+      uint32_t request_id,
+      const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
+      network::mojom::NetworkServiceClient::OnCertificateRequestedCallback
+          callback) override {
+    NOTREACHED();
+  }
+
+  void OnSSLCertificateError(uint32_t process_id,
+                             uint32_t routing_id,
+                             uint32_t request_id,
+                             int32_t resource_type,
+                             const GURL& url,
+                             const net::SSLInfo& ssl_info,
+                             bool fatal,
+                             OnSSLCertificateErrorCallback response) override {
+    std::move(response).Run(net::OK);
+  }
+
+ private:
+  mojo::Binding<network::mojom::NetworkServiceClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(AllowBadCertsNetworkServiceClient);
+};
+
+// Test |use_to_validate_certs|, which is required by AIA fetching, among other
+// things.
+TEST_F(NetworkServiceTestWithService, AIAFetching) {
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  mojom::NetworkServiceClientPtr network_service_client;
+  context_params->use_to_validate_certs = true;
+
+  // Have to allow bad certs when using
+  // SpawnedTestServer::SSLOptions::CERT_AUTO_AIA_INTERMEDIATE.
+  AllowBadCertsNetworkServiceClient allow_bad_certs_client(
+      mojo::MakeRequest(&network_service_client));
+
+  network_service_->CreateNetworkContext(mojo::MakeRequest(&network_context_),
+                                         std::move(context_params));
+
+  net::SpawnedTestServer::SSLOptions ssl_options(
+      net::SpawnedTestServer::SSLOptions::CERT_AUTO_AIA_INTERMEDIATE);
+  net::SpawnedTestServer test_server(net::SpawnedTestServer::TYPE_HTTPS,
+                                     ssl_options,
+                                     base::FilePath(kServicesTestData));
+  ASSERT_TRUE(test_server.Start());
+
+  LoadURL(test_server.GetURL("/echo"),
+          mojom::kURLLoadOptionSendSSLInfoWithResponse);
+  EXPECT_EQ(net::OK, client()->completion_status().error_code);
+  EXPECT_EQ(
+      0u, client()->response_head().cert_status & net::CERT_STATUS_ALL_ERRORS);
+  ASSERT_TRUE(client()->ssl_info());
+  ASSERT_TRUE(client()->ssl_info()->cert);
+  EXPECT_EQ(2u, client()->ssl_info()->cert->intermediate_buffers().size());
+  ASSERT_TRUE(client()->ssl_info()->unverified_cert);
+  EXPECT_EQ(
+      0u, client()->ssl_info()->unverified_cert->intermediate_buffers().size());
+}
+#endif  // !defined(OS_IOS)
+
+// Check that destroying a NetworkContext with |use_to_validate_certs| set
+// destroys all other NetworkContexts.
+TEST_F(NetworkServiceTestWithService,
+       DestryingUseToValidateCertsContextDestroysOtherContexts) {
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->use_to_validate_certs = true;
+  mojom::NetworkContextPtr cert_validating_network_context;
+  network_service_->CreateNetworkContext(
+      mojo::MakeRequest(&cert_validating_network_context),
+      std::move(context_params));
+
+  base::RunLoop run_loop;
+  mojom::NetworkContextPtr network_context;
+  network_service_->CreateNetworkContext(mojo::MakeRequest(&network_context),
+                                         CreateContextParams());
+  network_context.set_connection_error_handler(run_loop.QuitClosure());
+
+  // Destroying |cert_validating_network_context| should result in destroying
+  // |network_context| as well.
+  cert_validating_network_context.reset();
+  run_loop.Run();
+  EXPECT_TRUE(network_context.encountered_error());
+}
+
 class TestNetworkChangeManagerClient
     : public mojom::NetworkChangeManagerClient {
  public:
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 3b47004..46462f3 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -158,6 +158,18 @@
   // these logs will be extracted and verified; other SCTs will be treated as
   // unrecognized.
   array<CTLogInfo> ct_logs;
+
+  // Sets whether the NetworkContext should be used by all other
+  // NetworkContexts to validate certs (OCSP, AIA, etc) on some platforms.
+  // May only be set to true the first NetworkContext created using the
+  // NetworkService.  Destroying a NetworkContext created with this set to true
+  // will destroy all other NetworkContexts.
+  //
+  // A failure to mark a NetworkContext as being used by certificate verification may result in incorrect certificate validation behaviors, such as the inability to verify EV certificates, to build paths when intermediates are missing, and to enforce revocation checking when it was requested via policy.
+  //
+  // TODO(mmenke): Once NSS is not used on any platform for certificate verification, we should consider using each URLRequestContext to do its own validation and
+  // removing this parameter.
+  bool use_to_validate_certs = false;
 };
 
 struct NetworkConditions {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index a864618e..3d028528 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -1,9 +1,13 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
-  "AAAAA2 See gpu/generate_buildbot_json.py to make changes": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
   "Android FYI 32 Vk Release (Nexus 5X)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -35,6 +39,10 @@
         "test": "angle_end2end_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -94,7 +102,9 @@
         "args": [
           "--deqp-egl-display-type=angle-vulkan",
           "--enable-xml-result-parsing",
-          "--shard-timeout=500"
+          "--shard-timeout=500",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "name": "angle_deqp_gles2_vulkan_tests",
         "swarming": {
@@ -133,6 +143,10 @@
   "Android FYI 64 Vk Release (Nexus 5X)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -164,6 +178,10 @@
         "test": "angle_end2end_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -223,7 +241,9 @@
         "args": [
           "--deqp-egl-display-type=angle-vulkan",
           "--enable-xml-result-parsing",
-          "--shard-timeout=500"
+          "--shard-timeout=500",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "name": "angle_deqp_gles2_vulkan_tests",
         "swarming": {
@@ -262,6 +282,10 @@
   "Android FYI Release (NVIDIA Shield TV)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -293,6 +317,10 @@
         "test": "angle_end2end_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -324,6 +352,10 @@
         "test": "angle_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -355,6 +387,10 @@
         "test": "gl_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -386,6 +422,10 @@
         "test": "gl_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -701,6 +741,10 @@
   "Android FYI Release (Nexus 5)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -732,6 +776,10 @@
         "test": "angle_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -763,6 +811,10 @@
         "test": "gl_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -794,6 +846,10 @@
         "test": "gl_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1109,6 +1165,10 @@
   "Android FYI Release (Nexus 5X)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1139,6 +1199,10 @@
         "test": "angle_end2end_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1442,6 +1506,10 @@
   "Android FYI Release (Nexus 6)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1473,6 +1541,10 @@
         "test": "angle_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1504,6 +1576,10 @@
         "test": "gl_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1535,6 +1611,10 @@
         "test": "gl_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1831,6 +1911,10 @@
   "Android FYI Release (Nexus 6P)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1862,6 +1946,10 @@
         "test": "angle_end2end_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1893,6 +1981,10 @@
         "test": "angle_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1924,6 +2016,10 @@
         "test": "gl_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -1955,6 +2051,10 @@
         "test": "gl_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -2270,6 +2370,10 @@
   "Android FYI Release (Nexus 9)": {
     "gtest_tests": [
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -2301,6 +2405,10 @@
         "test": "angle_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -2332,6 +2440,10 @@
         "test": "gl_tests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -2363,6 +2475,10 @@
         "test": "gl_unittests"
       },
       {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
@@ -2680,7 +2796,9 @@
       {
         "args": [
           "--enable-xml-result-parsing",
-          "--shard-timeout=500"
+          "--shard-timeout=500",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "name": "angle_deqp_egl_gles_tests",
         "swarming": {
@@ -2717,7 +2835,9 @@
         "args": [
           "--deqp-egl-display-type=angle-gles",
           "--enable-xml-result-parsing",
-          "--shard-timeout=500"
+          "--shard-timeout=500",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "name": "angle_deqp_gles2_gles_tests",
         "swarming": {
@@ -2753,7 +2873,9 @@
       {
         "args": [
           "--enable-xml-result-parsing",
-          "--shard-timeout=500"
+          "--shard-timeout=500",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "name": "angle_deqp_gles3_gles_tests",
         "swarming": {
@@ -10403,6 +10525,22 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-23.20.16.4877",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "angle_gles1_conformance_tests"
+      },
+      {
+        "args": [
           "--test-launcher-retry-limit=0"
         ],
         "swarming": {
@@ -10441,6 +10579,24 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests",
+          "--use-cmd-decoder=passthrough"
+        ],
+        "name": "gl_tests_passthrough",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-23.20.16.4877",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "gl_tests"
+      },
+      {
+        "args": [
           "--use-gpu-in-tests"
         ],
         "swarming": {
@@ -10517,6 +10673,44 @@
           ]
         },
         "test": "swiftshader_unittests"
+      },
+      {
+        "args": [
+          "--use-angle=d3d9",
+          "--use-test-data-path",
+          "--test_video_data=test-25fps.h264:320:240:250:258:::1"
+        ],
+        "name": "video_decode_accelerator_d3d9_unittest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-23.20.16.4877",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "video_decode_accelerator_unittest"
+      },
+      {
+        "args": [
+          "--use-angle=gl",
+          "--use-test-data-path",
+          "--test_video_data=test-25fps.h264:320:240:250:258:::1"
+        ],
+        "name": "video_decode_accelerator_gl_unittest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:5912-23.20.16.4877",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "video_decode_accelerator_unittest"
       }
     ],
     "isolated_scripts": [
@@ -10695,6 +10889,31 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:1cb3-23.21.13.8816",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "angle_gles1_conformance_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-23.21.13.8816\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
+      },
+      {
+        "args": [
           "--test-launcher-retry-limit=0"
         ],
         "swarming": {
@@ -10751,6 +10970,33 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests",
+          "--use-cmd-decoder=passthrough"
+        ],
+        "name": "gl_tests_passthrough",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:1cb3-23.21.13.8816",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-23.21.13.8816\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
+      },
+      {
+        "args": [
           "--use-gpu-in-tests"
         ],
         "swarming": {
@@ -10872,6 +11118,62 @@
           ],
           "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
+      },
+      {
+        "args": [
+          "--use-angle=d3d9",
+          "--use-test-data-path",
+          "--test_video_data=test-25fps.h264:320:240:250:258:::1"
+        ],
+        "name": "video_decode_accelerator_d3d9_unittest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:1cb3-23.21.13.8816",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "video_decode_accelerator_unittest",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-23.21.13.8816\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
+      },
+      {
+        "args": [
+          "--use-angle=gl",
+          "--use-test-data-path",
+          "--test_video_data=test-25fps.h264:320:240:250:258:::1"
+        ],
+        "name": "video_decode_accelerator_gl_unittest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:1cb3-23.21.13.8816",
+              "os": "Windows-10",
+              "pool": "Chrome-GPU"
+            }
+          ]
+        },
+        "test": "video_decode_accelerator_unittest",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-23.21.13.8816\", \"os\": \"Windows-10\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index 73af154..87d4c192 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -1,6 +1,6 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
-  "AAAAA2 See gpu/generate_buildbot_json.py to make changes": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
   "Android Release (Nexus 5X)": {
     "isolated_scripts": [
       {
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index 8e48b781..d9863c3 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -1,6 +1,6 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
-  "AAAAA2 See gpu/generate_buildbot_json.py to make changes": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
   "Android V8 FYI Release (Nexus 5X)": {
     "isolated_scripts": [
       {
@@ -974,8 +974,7 @@
   "V8 Android GN (dbg)": {
     "additional_compile_targets": [
       "chrome_public_apk"
-    ],
-    "gtest_tests": []
+    ]
   },
   "V8 Linux GN": {
     "additional_compile_targets": [
diff --git a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
index 496d73c..f85af21 100644
--- a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
@@ -121,11 +121,6 @@
 -IOThreadBrowserTestWithHangingPacRequest.Shutdown
 -ProxySettingsApiTest.ProxyEventsParseError
 
-# SetURLRequestContextForNSSHttpIO / SetGlobalCertNetFetcher are not called
-# in the network process.
--SSLUITest.TestHTTPSOCSPRevoked/0
--SSLUITest.TestHTTPSOCSPRevoked/1
-
 # http://crbug.com/721414
 # TODO(rockot): add support for webRequest API.
 -ExtensionWebRequestApiTest.WebRequestBlocking
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index 76d2474e..9090bae 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -71,6 +71,23 @@
   return -1 # pragma: no cover
 
 
+class GPUTelemetryTestGenerator(BaseGenerator):
+  def __init__(self, bb_gen):
+    super(GPUTelemetryTestGenerator, self).__init__(bb_gen)
+
+  def generate(self, waterfall, tester_name, tester_config, input_tests):
+    isolated_scripts = []
+    for test_name, test_config in sorted(input_tests.iteritems()):
+      test = self.bb_gen.generate_gpu_telemetry_test(
+        waterfall, tester_name, tester_config, test_name, test_config)
+      if test:
+        isolated_scripts.append(test)
+    return isolated_scripts
+
+  def sort(self, tests):
+    return sorted(tests, key=lambda x: x['name'])
+
+
 class GTestGenerator(BaseGenerator):
   def __init__(self, bb_gen):
     super(GTestGenerator, self).__init__(bb_gen)
@@ -214,9 +231,15 @@
       raise BBGenErr('Failed to parse pyl file "%s": %s' %
                      (filename, e)) # pragma: no cover
 
+  # TOOD(kbr): require that os_type be specified for all bots in waterfalls.pyl.
+  # Currently it is only mandatory for bots which run GPU tests. Change these to
+  # use [] instead of .get().
   def is_android(self, tester_config):
     return tester_config.get('os_type') == 'android'
 
+  def is_linux(self, tester_config):
+    return tester_config.get('os_type') == 'linux'
+
   def get_exception_for_test(self, test_name, test_config):
     # gtests may have both "test" and "name" fields, and usually, if the "name"
     # field is specified, it means that the same test is being repurposed
@@ -228,11 +251,8 @@
     else:
       return self.exceptions.get(test_name)
 
-  def should_run_on_tester(self, waterfall, tester_name, tester_config,
-                           test_name, test_config, test_suite_type=None):
-    # TODO(kbr): until this script is merged with the GPU test generator, some
-    # arguments will be unused.
-    del tester_config
+  def should_run_on_tester(self, waterfall, tester_name,test_name, test_config,
+                           test_suite_type=None):
     # Currently, the only reason a test should not run on a given tester is that
     # it's in the exceptions. (Once the GPU waterfall generation script is
     # incorporated here, the rules will become more complex.)
@@ -282,24 +302,17 @@
       return []
     return exception.get('key_removals', {}).get(tester_name, [])
 
-  def maybe_fixup_args_array(self, arr):
-    # The incoming array of strings may be an array of command line
-    # arguments. To make it easier to turn on certain features per-bot
-    # or per-test-suite, look specifically for any --enable-features
-    # flags, and merge them into comma-separated lists. (This might
-    # need to be extended to handle other arguments in the future,
-    # too.)
-    enable_str = '--enable-features='
-    enable_str_len = len(enable_str)
-    enable_features_args = []
+  def merge_command_line_args(self, arr, prefix, splitter):
+    prefix_len = len(prefix)
     idx = 0
     first_idx = -1
+    accumulated_args = []
     while idx < len(arr):
       flag = arr[idx]
       delete_current_entry = False
-      if flag.startswith(enable_str):
-        arg = flag[enable_str_len:]
-        enable_features_args.extend(arg.split(','))
+      if flag.startswith(prefix):
+        arg = flag[prefix_len:]
+        accumulated_args.extend(arg.split(splitter))
         if first_idx < 0:
           first_idx = idx
         else:
@@ -309,7 +322,23 @@
       else:
         idx += 1
     if first_idx >= 0:
-      arr[first_idx] = enable_str + ','.join(enable_features_args)
+      arr[first_idx] = prefix + splitter.join(accumulated_args)
+    return arr
+
+  def maybe_fixup_args_array(self, arr):
+    # The incoming array of strings may be an array of command line
+    # arguments. To make it easier to turn on certain features per-bot or
+    # per-test-suite, look specifically for certain flags and merge them
+    # appropriately.
+    #   --enable-features=Feature1 --enable-features=Feature2
+    # are merged to:
+    #   --enable-features=Feature1,Feature2
+    # and:
+    #   --extra-browser-args=arg1 --extra-browser-args=arg2
+    # are merged to:
+    #   --extra-browser-args=arg1 arg2
+    arr = self.merge_command_line_args(arr, '--enable-features=', ',')
+    arr = self.merge_command_line_args(arr, '--extra-browser-args=', ' ')
     return arr
 
   def dictionary_merge(self, a, b, path=None, update=True):
@@ -361,6 +390,20 @@
       generated_test['args'] = self.maybe_fixup_args_array(
         generated_test.get('args', []) + tester_config.get('args', []))
 
+    def add_conditional_args(key, fn):
+      if key in generated_test:
+        if fn(tester_config):
+          if not 'args' in generated_test:
+            generated_test['args'] = []
+          generated_test['args'] += generated_test[key]
+        # Don't put the conditional args in the JSON.
+        generated_test.pop(key)
+
+    add_conditional_args('desktop_args', lambda cfg: not self.is_android(cfg))
+    add_conditional_args('linux_args', self.is_linux)
+    add_conditional_args('android_args', self.is_android)
+
+
   def initialize_swarming_dictionary_for_test(self, generated_test,
                                               tester_config):
     if 'swarming' not in generated_test:
@@ -429,7 +472,7 @@
   def generate_gtest(self, waterfall, tester_name, tester_config, test_name,
                      test_config):
     if not self.should_run_on_tester(
-        waterfall, tester_name, tester_config, test_name, test_config,
+        waterfall, tester_name, test_name, test_config,
         TestSuiteTypes.GTEST):
       return None
     result = copy.deepcopy(test_config)
@@ -441,9 +484,8 @@
     self.initialize_args_for_test(result, tester_config)
     if self.is_android(tester_config) and tester_config.get('use_swarming',
                                                             True):
-      if 'args' not in result:
-        result['args'] = []
-      result['args'].append('--gs-results-bucket=chromium-result-details')
+      args = result.get('args', [])
+      args.append('--gs-results-bucket=chromium-result-details')
       if (result['swarming']['can_use_on_swarming_builders'] and not
           tester_config.get('skip_merge_script', False)):
         result['merge'] = {
@@ -475,8 +517,9 @@
             'name': 'shard #${SHARD_INDEX} logcats',
           },
         ]
-      if not tester_config.get('skip_device_recovery', False):
-        result['args'].append('--recover-devices')
+      args.append('--recover-devices')
+      if args:
+        result['args'] = args
 
     result = self.update_and_cleanup_test(result, test_name, tester_name,
                                           waterfall)
@@ -485,13 +528,14 @@
 
   def generate_isolated_script_test(self, waterfall, tester_name, tester_config,
                                     test_name, test_config):
-    if not self.should_run_on_tester(waterfall, tester_name, tester_config,
-                                     test_name, test_config):
+    if not self.should_run_on_tester(waterfall, tester_name, test_name,
+                                     test_config):
       return None
     result = copy.deepcopy(test_config)
     result['isolate_name'] = result.get('isolate_name', test_name)
     result['name'] = test_name
     self.initialize_swarming_dictionary_for_test(result, tester_config)
+    self.initialize_args_for_test(result, tester_config)
     result = self.update_and_cleanup_test(result, test_name, tester_name,
                                           waterfall)
     self.add_common_test_properties(result, tester_config)
@@ -499,8 +543,9 @@
 
   def generate_script_test(self, waterfall, tester_name, tester_config,
                            test_name, test_config):
-    if not self.should_run_on_tester(waterfall, tester_name, tester_config,
-                                     test_name, test_config):
+    del tester_config
+    if not self.should_run_on_tester(waterfall, tester_name, test_name,
+                                     test_config):
       return None
     result = {
       'name': test_name,
@@ -512,8 +557,9 @@
 
   def generate_junit_test(self, waterfall, tester_name, tester_config,
                           test_name, test_config):
-    if not self.should_run_on_tester(waterfall, tester_name, tester_config,
-                                     test_name, test_config):
+    del tester_config
+    if not self.should_run_on_tester(waterfall, tester_name, test_name,
+                                     test_config):
       return None
     result = {
       'test': test_name,
@@ -522,8 +568,9 @@
 
   def generate_instrumentation_test(self, waterfall, tester_name, tester_config,
                                     test_name, test_config):
-    if not self.should_run_on_tester(waterfall, tester_name, tester_config,
-                                     test_name, test_config):
+    del tester_config
+    if not self.should_run_on_tester(waterfall, tester_name, test_name,
+                                     test_config):
       return None
     result = copy.deepcopy(test_config)
     if 'test' in result and result['test'] != test_name:
@@ -534,9 +581,61 @@
                                           waterfall)
     return result
 
+  def substitute_gpu_args(self, tester_config, args):
+    substitutions = {
+      # Any machine in waterfalls.pyl which desires to run GPU tests
+      # must provide the os_type key.
+      'os_type': tester_config['os_type'],
+      'gpu_vendor_id': '0',
+      'gpu_device_id': '0',
+    }
+    dimension_set = tester_config['swarming']['dimension_sets'][0]
+    if 'gpu' in dimension_set:
+      # First remove the driver version, then split into vendor and device.
+      gpu = dimension_set['gpu']
+      gpu = gpu.split('-')[0].split(':')
+      substitutions['gpu_vendor_id'] = gpu[0]
+      substitutions['gpu_device_id'] = gpu[1]
+    return [string.Template(arg).safe_substitute(substitutions) for arg in args]
+
+  def generate_gpu_telemetry_test(self, waterfall, tester_name, tester_config,
+                                  test_name, test_config):
+    # These are all just specializations of isolated script tests with
+    # a bunch of boilerplate command line arguments added.
+
+    # The step name must end in 'test' or 'tests' in order for the
+    # results to automatically show up on the flakiness dashboard.
+    # (At least, this was true some time ago.) Continue to use this
+    # naming convention for the time being to minimize changes.
+    step_name = test_config.get('name', test_name)
+    if not (step_name.endswith('test') or step_name.endswith('tests')):
+      step_name = '%s_tests' % step_name
+    result = self.generate_isolated_script_test(
+      waterfall, tester_name, tester_config, step_name, test_config)
+    if not result:
+      return None
+    result['isolate_name'] = 'telemetry_gpu_integration_test'
+    args = result.get('args', [])
+    test_to_run = result.pop('telemetry_test_name', test_name)
+    args = [
+      test_to_run,
+      '--show-stdout',
+      '--browser=%s' % tester_config['browser_config'],
+      # --passthrough displays more of the logging in Telemetry when
+      # run via typ, in particular some of the warnings about tests
+      # being expected to fail, but passing.
+      '--passthrough',
+      '-v',
+      '--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc',
+    ] + args
+    result['args'] = self.maybe_fixup_args_array(self.substitute_gpu_args(
+      tester_config, args))
+    return result
+
   def get_test_generator_map(self):
     return {
       'cts_tests': CTSGenerator(self),
+      'gpu_telemetry_tests': GPUTelemetryTestGenerator(self),
       'gtest_tests': GTestGenerator(self),
       'instrumentation_tests': InstrumentationTestGenerator(self),
       'isolated_scripts': IsolatedScriptTestGenerator(self),
@@ -544,6 +643,13 @@
       'scripts': ScriptGenerator(self),
     }
 
+  def get_test_type_remapper(self):
+    return {
+      # These are a specialization of isolated_scripts with a bunch of
+      # boilerplate command line arguments added to each one.
+      'gpu_telemetry_tests': 'isolated_scripts',
+    }
+
   def check_composition_test_suites(self):
     # Pre-pass to catch errors reliably.
     for name, value in self.test_suites.iteritems():
@@ -607,6 +713,7 @@
   def generate_waterfall_json(self, waterfall):
     all_tests = {}
     generator_map = self.get_test_generator_map()
+    test_type_remapper = self.get_test_type_remapper()
     for name, config in waterfall['machines'].iteritems():
       tests = {}
       # Copy only well-understood entries in the machine's configuration
@@ -620,8 +727,14 @@
             test_type, name, waterfall['name']) # pragma: no cover
         test_generator = generator_map[test_type]
         try:
-          tests[test_type] = test_generator.sort(test_generator.generate(
-            waterfall, name, config, input_tests))
+          # Let multiple kinds of generators generate the same kinds
+          # of tests. For example, gpu_telemetry_tests are a
+          # specialization of isolated_scripts.
+          new_tests = test_generator.generate(
+            waterfall, name, config, input_tests)
+          remapped_test_type = test_type_remapper.get(test_type, test_type)
+          tests[remapped_test_type] = test_generator.sort(
+            tests.get(remapped_test_type, []) + new_tests)
         except Exception as e:
           raise self.generation_error(test_type, name, waterfall['name'], e)
       all_tests[name] = tests
@@ -657,17 +770,37 @@
       bot_names.add(l[l.rindex('/') + 1:l.rindex('"')])
     return bot_names
 
+  def get_bots_that_do_not_actually_exist(self):
+    # Some of the bots on the chromium.gpu.fyi waterfall in particular
+    # are defined only to be mirrored into trybots, and don't actually
+    # exist on any of the waterfalls or consoles.
+    return [
+      'Optional Android Release (Nexus 5X)',
+      'Optional Linux Release (Intel HD 630)',
+      'Optional Linux Release (NVIDIA)',
+      'Optional Mac Release (Intel)',
+      'Optional Mac Retina Release (AMD)',
+      'Optional Mac Retina Release (NVIDIA)',
+      'Optional Win10 Release (Intel HD 630)',
+      'Optional Win10 Release (NVIDIA)',
+      'Win7 ANGLE Tryserver (AMD)',
+    ]
+
   def check_input_file_consistency(self):
     self.load_configuration_files()
     self.check_composition_test_suites()
 
     # All bots should exist.
     bot_names = self.get_valid_bot_names()
+    bots_that_dont_exist = self.get_bots_that_do_not_actually_exist()
     for waterfall in self.waterfalls:
       for bot_name in waterfall['machines']:
+        if bot_name in bots_that_dont_exist:
+          continue  # pragma: no cover
         if bot_name not in bot_names:
           if waterfall['name'] in ['chromium.android.fyi', 'chromium.fyi',
-                                   'chromium.lkgr', 'client.v8.chromium']:
+                                   'chromium.lkgr', 'client.v8.chromium',
+                                   'client.v8.fyi']:
             # TODO(thakis): Remove this once these bots move to luci.
             continue  # pragma: no cover
           if waterfall['name'] in ['tryserver.webrtc']:
diff --git a/testing/buildbot/generate_buildbot_json_unittest.py b/testing/buildbot/generate_buildbot_json_unittest.py
index bd0794e0..7183696 100755
--- a/testing/buildbot/generate_buildbot_json_unittest.py
+++ b/testing/buildbot/generate_buildbot_json_unittest.py
@@ -100,6 +100,22 @@
 ]
 """
 
+FOO_LINUX_GTESTS_WATERFALL = """\
+[
+  {
+    'name': 'chromium.test',
+    'machines': {
+      'Fake Tester': {
+        'os_type': 'linux',
+        'test_suites': {
+          'gtest_tests': 'foo_tests',
+        },
+      },
+    },
+  },
+]
+"""
+
 COMPOSITION_GTEST_SUITE_WATERFALL = """\
 [
   {
@@ -208,6 +224,30 @@
 ]
 """
 
+FOO_GPU_TELEMETRY_TEST_WATERFALL = """\
+[
+  {
+    'name': 'chromium.test',
+    'machines': {
+      'Fake Tester': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '10de:1cb3',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'composition_tests',
+        },
+      },
+    },
+  },
+]
+"""
+
 UNKNOWN_TEST_SUITE_WATERFALL = """\
 [
   {
@@ -262,7 +302,6 @@
           ],
         },
         'os_type': 'android',
-        'skip_device_recovery': True,
         'test_suites': {
           'gtest_tests': 'foo_tests',
         },
@@ -347,6 +386,18 @@
 }
 """
 
+FOO_TEST_SUITE_WITH_LINUX_ARGS = """\
+{
+  'foo_tests': {
+    'foo_test': {
+      'linux_args': [
+        '--no-xvfb',
+      ],
+    },
+  },
+}
+"""
+
 FOO_TEST_SUITE_WITH_ENABLE_FEATURES = """\
 {
   'foo_tests': {
@@ -454,6 +505,21 @@
 }
 """
 
+COMPOSITION_SUITE_WITH_NAME_NOT_ENDING_IN_TEST = """\
+{
+  'foo_tests': {
+    'foo': {},
+  },
+  'bar_tests': {
+    'bar_test': {},
+  },
+  'composition_tests': [
+    'foo_tests',
+    'bar_tests',
+  ],
+}
+"""
+
 EMPTY_EXCEPTIONS = """\
 {
 }
@@ -684,6 +750,26 @@
 }
 """
 
+LINUX_ARGS_OUTPUT = """\
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Fake Tester": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--no-xvfb"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "foo_test"
+      }
+    ]
+  }
+}
+"""
+
 MERGED_ENABLE_FEATURES_OUTPUT = """\
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
@@ -845,6 +931,37 @@
 }
 """
 
+GPU_TELEMETRY_TEST_OUTPUT = """\
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Fake Tester": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "foo",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "name": "foo_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:1cb3"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
+"""
+
 ANDROID_WATERFALL_OUTPUT = """\
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
@@ -861,7 +978,8 @@
     "gtest_tests": [
       {
         "args": [
-          "--gs-results-bucket=chromium-result-details"
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
         ],
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1089,6 +1207,14 @@
     fbb.files['chromium.test.json'] = MERGED_ENABLE_FEATURES_OUTPUT
     fbb.check_output_file_consistency(verbose=True)
 
+  def test_linux_args(self):
+    fbb = FakeBBGen(FOO_LINUX_GTESTS_WATERFALL,
+                    FOO_TEST_SUITE_WITH_LINUX_ARGS,
+                    EMPTY_EXCEPTIONS,
+                    LUCI_MILO_CFG)
+    fbb.files['chromium.test.json'] = LINUX_ARGS_OUTPUT
+    fbb.check_output_file_consistency(verbose=True)
+
   def test_test_filtering(self):
     fbb = FakeBBGen(COMPOSITION_GTEST_SUITE_WATERFALL,
                     GOOD_COMPOSITION_TEST_SUITES,
@@ -1153,6 +1279,14 @@
     fbb.files['chromium.test.json'] = INSTRUMENTATION_TEST_OUTPUT
     fbb.check_output_file_consistency(verbose=True)
 
+  def test_gpu_telemetry_tests(self):
+    fbb = FakeBBGen(FOO_GPU_TELEMETRY_TEST_WATERFALL,
+                    COMPOSITION_SUITE_WITH_NAME_NOT_ENDING_IN_TEST,
+                    NO_BAR_TEST_EXCEPTIONS,
+                    LUCI_MILO_CFG)
+    fbb.files['chromium.test.json'] = GPU_TELEMETRY_TEST_OUTPUT
+    fbb.check_output_file_consistency(verbose=True)
+
   def test_instrumentation_tests_with_different_names(self):
     fbb = FakeBBGen(FOO_INSTRUMENTATION_TEST_WATERFALL,
                     INSTRUMENTATION_TESTS_WITH_DIFFERENT_NAMES,
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index aaaec0e..9e4f2e5 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -49,6 +49,23 @@
       },
     },
   },
+  'angle_end2end_tests': {
+    'remove_from': [
+      # chromium.gpu.fyi
+      # TODO(ynovikov) Investigate why the test breaks on older devices.
+      'Android FYI Release (Nexus 5)',
+      'Android FYI Release (Nexus 6)',
+      'Android FYI Release (Nexus 9)',
+      # Temporarily disabled due to AMDGPU-PRO issues crbug.com/786219
+      'Linux FYI Release (AMD R7 240)',
+    ],
+  },
+  'angle_perftests': {
+    'remove_from': [
+      # anglebug.com/2433
+      'Android FYI Release (Nexus 6)',
+    ],
+  },
   'angle_unittests': {
     'remove_from': [
       # chromium.fyi
@@ -56,6 +73,15 @@
       'Chromium Mac 10.11',
       'Chromium Mac 10.11 Force Mac Toolchain',
       'Chromium Mac 10.13',
+      # chromium.gpu and chromium.gpu.fyi
+      # On Android, these are already run on the main waterfall.
+      # Run them on the one-off Android FYI bots, though.
+      'Android Release (Nexus 5X)',
+      'Android FYI Release (Nexus 5X)',
+      # Remove these from the optional GPU tryservers as they're
+      # already run on the main ones.
+      'Optional Linux Release (Intel HD 630)',
+      'Optional Linux Release (NVIDIA)',
     ],
     'modifications': {
       'Linux ASan LSan Tests (1)': {
@@ -1212,6 +1238,15 @@
       'UBSanVptr Linux',
       # chromium.fyi
       'Out of Process Profiling Linux',
+      # chromium.gpu
+      # On Android, these are already run on the main waterfall.
+      # Run them on the one-off Android FYI bots, though.
+      'Android Release (Nexus 5X)',
+      # chromium.gpu.fyi
+      'Android FYI 32 Vk Release (Nexus 5X)',
+      'Android FYI 64 Vk Release (Nexus 5X)',
+      'Android FYI Release (Nexus 5X)',
+      'Linux FYI Ozone (Intel)',
       # chromium.memory
       # Can't run on MSAN because gl_tests uses the hardware driver,
       # which isn't instrumented.
@@ -1302,6 +1337,12 @@
     'remove_from': [
       # chromium.fyi
       'Out of Process Profiling Linux',
+      # chromium.gpu.fyi
+      # On Android, these are already run on the main waterfall.
+      # Run them on the one-off Android FYI bots, though.
+      'Android FYI Release (Nexus 5X)',
+      # Temporarily disabled due to AMDGPU-PRO issues crbug.com/786219
+      'Linux FYI Release (AMD R7 240)',
     ],
     'modifications': {
       # chromium.android
@@ -1409,6 +1450,20 @@
       'Linux - Future (dbg)'
     ],
   },
+  'info_collection_tests': {
+    'remove_from': [
+      # chromium.gpu.fyi
+      # The Mac ASAN swarming bot runs tests on two different GPU
+      # types, so we can't have one expected vendor ID / device ID.
+      'Mac FYI GPU ASAN Release',
+      # client.v8.fyi
+      'Android V8 FYI Release (Nexus 5X)',
+      'Linux V8 FYI Release (NVIDIA)',
+      'Linux V8 FYI Release - concurrent marking (NVIDIA)',
+      'Mac V8 FYI Release (Intel)',
+      'Win V8 FYI Release (NVIDIA)',
+    ],
+  },
   'install_static_unittests': {
     'remove_from': [
       # chromium.fyi
@@ -1850,6 +1905,9 @@
     'remove_from': [
       # chromium.clang
       'ToTLinuxMSan',  # https://crbug.com/831676
+      # chromium.gpu.fyi
+      # The face and barcode detection tests fail on the Mac Pros.
+      'Mac Pro FYI Release (AMD)',
       # chromium.fyi
       'mac-views-rel',
       'Chromium Mac 10.11',
@@ -2110,7 +2168,34 @@
           '--browser=debug',
         ],
       },
-    }
+    },
+  },
+  'swiftshader_unittests': {
+    'remove_from': [
+      # Save capacity on the hardware where we have only a few machines.
+      'Mac FYI Experimental Release (Intel)',
+      'Mac FYI Experimental Retina Release (AMD)',
+      'Mac FYI Experimental Retina Release (NVIDIA)',
+      'Mac Pro FYI Release (AMD)',
+    ],
+  },
+  'tab_capture_end2end_tests': {
+    # Run these only on Release bots, and don't run them on TSAN.
+    'remove_from': [
+      # chromium.gpu
+      'Linux Debug (NVIDIA)',
+      'Mac Debug (Intel)',
+      'Mac Retina Debug (AMD)',
+      'Win10 Debug (NVIDIA)',
+      # chromium.gpu.fyi
+      'Linux FYI Debug (NVIDIA)',
+      'Linux FYI GPU TSAN Release',
+      'Mac FYI Debug (Intel)',
+      'Mac FYI Retina Debug (AMD)',
+      'Mac FYI Retina Debug (NVIDIA)',
+      'Win10 FYI Debug (NVIDIA)',
+      'Win7 FYI Debug (AMD)',
+    ],
   },
   'telemetry_perf_unittests': {
     'modifications': {
@@ -2376,6 +2461,14 @@
       },
     },
   },
+  'video_decode_accelerator_gl_unittest': {
+    'remove_from': [
+      # chromium.gpu.fyi
+      # Windows Intel doesn't have the GL extensions to support this test.
+      'Win10 FYI Release (Intel HD 630)',
+      'Win10 FYI Exp Release (Intel HD 630)',
+    ],
+  },
   'views_unittests': {
     'remove_from': [
       # chromium.linux
@@ -2398,6 +2491,12 @@
       'Win10 Tests x64 (dbg)',
     ],
   },
+  'viz_screenshot_sync_tests': {
+    'remove_from': [
+      # These tests don't work on TSAN right now.
+      'Linux FYI GPU TSAN Release',
+    ],
+  },
   'viz_unittests': {
     'remove_from': [
       # chromium.fyi
@@ -2462,6 +2561,18 @@
       },
     },
   },
+  'vr_browser_tests': {
+    'remove_from': [
+      # chromium.gpu.fyi
+      # TODO(kbr): remove the following two exceptions after switching
+      # to the new generate_buildbot_json script.
+      'Win10 FYI Exp Release (Intel HD 630)',
+      'Win7 FYI Release (AMD)',
+      # This exception probably needs to stay due to lack of capacity
+      # on the Win AMD bots.
+      'Win7 FYI Debug (AMD)',
+    ],
+  },
   'vr_pixeltests': {
     'remove_from': [
       # chromium.fyi
@@ -2475,6 +2586,59 @@
       'Win7 Tests (dbg)(1)',
     ],
   },
+  'webgl_conformance_d3d9_passthrough_tests': {
+    'remove_from': [
+      # TODO(jdarpinian): Re-enable when http://crbug.com/841789 is fixed.
+      'Win10 FYI Exp Release (Intel HD 630)',
+    ],
+  },
+  'webgl_conformance_d3d9_tests': {
+    'remove_from': [
+      # TODO(jdarpinian): Re-enable when http://crbug.com/841789 is fixed.
+      'Win10 FYI Exp Release (Intel HD 630)',
+    ],
+  },
+  'webgl_conformance_tests': {
+    'modifications': {
+      # On desktop platforms these don't take very long (~7 minutes),
+      # but on Android they take ~30 minutes and we want to use more
+      # shards than on desktop.
+      # chromium.gpu
+      'Android Release (Nexus 5X)': {
+        'swarming': {
+          'shards': 6,
+        },
+      },
+      'Mac FYI GPU ASAN Release': {
+        'args': [
+          '--is-asan',
+        ]
+      },
+    },
+  },
+  'webgl_conformance_gl_passthrough_tests': {
+    'remove_from': [
+      # crbug.com/555545 and crbug.com/649824:
+      # Disable webgl_conformance_gl_tests on some Win/AMD cards.
+      # Always fails on older cards, flaky on newer cards.
+      'Win7 FYI Debug (AMD)',
+      'Win7 FYI Release (AMD)',
+    ],
+  },
+  'webgl2_conformance_tests': {
+    'remove_from': [
+      # The Mac NVIDIA Retina bots don't have the capacity to run
+      # this test suite on mac_optional_gpu_tests_rel.
+      'Optional Mac Retina Release (NVIDIA)',
+    ],
+    'modifications': {
+      'Mac FYI GPU ASAN Release': {
+        'args': [
+          '--is-asan',
+        ]
+      },
+    },
+  },
   'webkit_layout_tests': {
     'remove_from': [
       # chromium.fyi
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 762535d..7832b18 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -942,6 +942,617 @@
     },
   },
 
+  'goma_gtests': {
+    'base_unittests': {},
+    'content_unittests': {},
+  },
+
+  'goma_mac_gtests': {
+    'base_unittests': {
+      'swarming': {
+        'dimension_sets': [
+          {
+            'os': 'Mac',
+          },
+        ],
+      }
+    },
+    'content_unittests': {
+      'swarming': {
+        'dimension_sets': [
+          {
+            'os': 'Mac',
+          },
+        ],
+      },
+    },
+  },
+
+  # BEGIN tests which run on the GPU bots
+
+  'gpu_angle_deqp_egl_d3d11_tests': {
+    'angle_deqp_egl_d3d11_tests': {
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-d3d11',
+      ],
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_egl_tests',
+    },
+  },
+
+  'gpu_angle_deqp_egl_gl_tests': {
+    'angle_deqp_egl_gl_tests': {
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gl',
+      ],
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_egl_tests',
+    },
+  },
+
+  'gpu_angle_deqp_egl_gles_tests': {
+    'angle_deqp_egl_gles_tests': {
+      # Only pass the display type to desktop. The Android runner doesn't support
+      # passing args to the executable but only one display type is supported on
+      # Android anyways. Regardless, this test is only run on Android right now.
+      'desktop_args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gles',
+      ],
+      'android_args': [
+        '--enable-xml-result-parsing',
+        '--shard-timeout=500',
+      ],
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_egl_tests',
+    },
+  },
+
+  'gpu_angle_deqp_gles2_d3d11_tests': {
+    'angle_deqp_gles2_d3d11_tests': {
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_gles2_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-d3d11',
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles2_gl_tests': {
+    'angle_deqp_gles2_gl_tests': {
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_gles2_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gl',
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles2_vulkan_tests': {
+    'angle_deqp_gles2_vulkan_tests': {
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_gles2_tests',
+      'args': [
+        '--deqp-egl-display-type=angle-vulkan',
+      ],
+      'desktop_args': [
+        '--test-launcher-batch-limit=400',
+      ],
+      'android_args': [
+        '--enable-xml-result-parsing',
+        '--shard-timeout=500'
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles3_d3d11_tests': {
+    # TODO(jmadill): Run this on ANGLE roll tryservers.
+    # Temporarily disabled on AMD Win 7 to prevent a recipe engine crash.
+    # TODO(jmadill): Re-enable there when http://crbug.com/713196 is fixed.
+    'angle_deqp_gles3_d3d11_tests': {
+      'swarming': {
+        'shards': 12,
+      },
+      'test': 'angle_deqp_gles3_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-d3d11',
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles3_gl_tests': {
+    'angle_deqp_gles3_gl_tests': {
+      'swarming': {
+        'shards': 12,
+      },
+      'test': 'angle_deqp_gles3_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gl',
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles31_d3d11_tests': {
+    'angle_deqp_gles31_d3d11_tests': {
+      'swarming': {
+        'shards': 6,
+      },
+      'test': 'angle_deqp_gles31_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-d3d11'
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles31_gl_tests': {
+    'angle_deqp_gles31_gl_tests': {
+      'swarming': {
+        'shards': 6,
+      },
+      'test': 'angle_deqp_gles31_tests',
+      'args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gl'
+      ],
+    },
+  },
+
+  'gpu_angle_deqp_gles_gtests': {
+    'angle_deqp_gles2_gles_tests': {
+      'swarming': {
+        'shards': 4,
+      },
+      'test': 'angle_deqp_gles2_tests',
+      'args': [
+        '--deqp-egl-display-type=angle-gles'
+      ],
+      'desktop_args': [
+        '--test-launcher-batch-limit=400',
+      ],
+      'android_args': [
+        '--enable-xml-result-parsing',
+        '--shard-timeout=500'
+      ],
+    },
+    'angle_deqp_gles3_gles_tests': {
+      'swarming': {
+        'shards': 12,
+      },
+      'test': 'angle_deqp_gles3_tests',
+      # Only pass the display type to desktop. The Android runner doesn't support
+      # passing args to the executable but only one display type is supported on
+      # Android anyways.
+      'desktop_args': [
+        '--test-launcher-batch-limit=400',
+        '--deqp-egl-display-type=angle-gles'
+      ],
+      'android_args': [
+        '--enable-xml-result-parsing',
+        '--shard-timeout=500'
+      ],
+    },
+  },
+
+  'gpu_angle_fyi_and_optional_win_specific_isolated_scripts': {
+    # TODO(jmadill): Run on Linux bots when possible.
+    'passthrough_command_buffer_perftests': {
+      'isolate_name': 'command_buffer_perftests',
+      'args': [
+        '-v',
+        '--use-cmd-decoder=passthrough',
+        '--use-angle=gl-null',
+      ],
+    },
+    # TODO(jmadill): Run on Linux bots when possible.
+    'validating_command_buffer_perftests': {
+      'isolate_name': 'command_buffer_perftests',
+      'args': [
+        '-v',
+        '--use-cmd-decoder=validating',
+        '--use-stub',
+      ],
+    },
+  },
+
+  'gpu_angle_unittests': {
+    'angle_unittests': {
+      'desktop_args': [
+        '--use-gpu-in-tests',
+        # ANGLE test retries deliberately disabled to prevent flakiness.
+        # http://crbug.com/669196
+        '--test-launcher-retry-limit=0'
+      ],
+      'linux_args': ['--no-xvfb'],
+    },
+  },
+
+  'gpu_common_and_optional_telemetry_tests': {
+    'info_collection': {
+      'args': [
+        '--expected-vendor-id',
+        '${gpu_vendor_id}',
+        '--expected-device-id',
+        '${gpu_device_id}',
+      ],
+    },
+  },
+
+  # GPU gtests which run on both the main and FYI waterfalls.
+  'gpu_common_gtests': {
+    'gl_tests': {
+      'desktop_args': [
+        '--use-gpu-in-tests',
+        '--use-cmd-decoder=validating',
+      ],
+    },
+    'gl_unittests': {
+      'desktop_args': ['--use-gpu-in-tests'],
+      'linux_args': ['--no-xvfb'],
+    },
+  },
+
+  'gpu_default_and_optional_win_specific_gtests': {
+    'vr_browser_tests': {
+      'args': [
+      '--enable-gpu',
+        '--test-launcher-bot-mode',
+        '--test-launcher-jobs=1',
+        '--gtest_filter=VrBrowserTest*:XrBrowserTest*',
+        '--enable-pixel-output-in-tests',
+        '--gtest_also_run_disabled_tests',
+      ],
+      'test': 'browser_tests',
+    },
+  },
+
+  'gpu_default_win_and_linux_specific_telemetry_tests': {
+    'viz_screenshot_sync': {
+      'args': [
+        '--dont-restore-color-profile-after-test',
+        # This test confirms that GPU compositing is working with OOP-D.
+        '--extra-browser-args=--enable-features=VizDisplayCompositor',
+      ],
+      'telemetry_test_name': 'screenshot_sync',
+    },
+  },
+
+  'gpu_desktop_specific_gtests': {
+    'tab_capture_end2end_tests': {
+      'args': [
+        '--enable-gpu',
+        '--test-launcher-bot-mode',
+        '--test-launcher-jobs=1',
+        '--gtest_filter=CastStreamingApiTestWithPixelOutput.EndToEnd*:TabCaptureApiPixelTest.EndToEnd*',
+      ],
+      'linux_args': [ '--no-xvfb' ],
+      'test': 'browser_tests',
+    },
+  },
+
+  'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests': {
+    'webgl_conformance_d3d11_passthrough': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--extra-browser-args=--use-angle=d3d11 --use-cmd-decoder=passthrough',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+    },
+    'webgl_conformance_d3d9_passthrough': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--extra-browser-args=--use-angle=d3d9 --use-cmd-decoder=passthrough',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+    },
+  },
+
+  # These run everywhere ANGLE does; currently, Win and Linux NVIDIA
+  # bots and Android, but not macOS yet.
+  'gpu_fyi_and_optional_isolated_scripts': {
+    'angle_perftests': {
+      'args': [
+        '-v',
+        # Tell the tests to exit after one frame for faster iteration.
+        '--one-frame-only',
+      ],
+    },
+  },
+
+  'gpu_fyi_and_optional_non_linux_gtests': {
+    # gpu_unittests is killing the Swarmed Linux GPU bots similarly to
+    # how content_unittests was: http://crbug.com/763498 .
+    'gpu_unittests': {
+    },
+  },
+
+  'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests': {
+    'webgl_conformance_d3d9_tests': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--extra-browser-args=--use-angle=d3d9 --use-cmd-decoder=validating',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+    },
+  },
+
+  'gpu_fyi_and_optional_and_win_angle_amd_gtests': {
+    'angle_end2end_tests': {
+      'desktop_args': [
+        '--use-gpu-in-tests',
+        # ANGLE test retries deliberately disabled to prevent flakiness.
+        # http://crbug.com/669196
+        '--test-launcher-retry-limit=0'
+      ],
+    },
+  },
+
+  'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests': {
+    # TODO(ynovikov): the old generator script said the white box
+    # tests are supposed to run everywhere angle_end2end_tests do, but
+    # they actually ran only on Windows and Linux.
+    'angle_white_box_tests': {
+      'desktop_args': [
+        # ANGLE test retries deliberately disabled to prevent flakiness.
+        # http://crbug.com/669196
+      '--test-launcher-retry-limit=0'
+      ],
+    },
+  },
+
+  'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests': {
+    'webgl_conformance_gl_passthrough': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--extra-browser-args=--use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+    },
+  },
+
+  'gpu_fyi_and_optional_win_specific_gtests': {
+    'angle_gles1_conformance_tests': {
+      'args': ['--use-gpu-in-tests']
+    },
+    'gl_tests_passthrough': {
+      'test': 'gl_tests',
+      'args': [
+        '--use-gpu-in-tests',
+        '--use-cmd-decoder=passthrough',
+      ],
+    },
+    'gles2_conform_d3d9_test': {
+      'args': [
+        '--use-gpu-in-tests',
+        '--use-angle=d3d9',
+      ],
+      'test': 'gles2_conform_test',
+    },
+    'gles2_conform_gl_test': {
+      'args': [
+        '--use-gpu-in-tests',
+        '--use-angle=gl',
+        '--disable-gpu-sandbox',
+      ],
+      'test': 'gles2_conform_test',
+    },
+    'video_decode_accelerator_d3d9_unittest': {
+      'args': [
+        '--use-angle=d3d9',
+        '--use-test-data-path',
+        '--test_video_data=test-25fps.h264:320:240:250:258:::1',
+      ],
+      'test': 'video_decode_accelerator_unittest',
+    },
+    'video_decode_accelerator_gl_unittest': {
+      'args': [
+        '--use-angle=gl',
+        '--use-test-data-path',
+        '--test_video_data=test-25fps.h264:320:240:250:258:::1',
+      ],
+      'test': 'video_decode_accelerator_unittest',
+    },
+  },
+
+  'gpu_fyi_desktop_specific_gtests': {
+    # The gles2_conform_tests are closed-source and deliberately only
+    # run on the FYI waterfall and the optional tryservers.
+    'gles2_conform_test': {
+      'args': ['--use-gpu-in-tests'],
+    },
+    'swiftshader_unittests': {
+    },
+  },
+
+  'gpu_fyi_mac_specific_gtests': {
+    # Face and barcode detection unit tests, which currently only run on
+    # Mac OS, and require physical hardware.
+    'services_unittests': {
+      'args': [
+        '--gtest_filter=*Detection*',
+        '--use-gpu-in-tests'
+      ],
+    },
+  },
+
+  'gpu_fyi_only_win_linux_intel_nvidia_specific_telemetry_tests': {
+    'webgl2_conformance_gl_passthrough_tests': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--webgl-conformance-version=2.0.1',
+        # The current working directory when run via isolate is
+        # out/Debug or out/Release. Reference this file relatively to
+        # it.
+        '--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json',
+        '--extra-browser-args=--use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough',
+      ],
+      'swarming': {
+        # These tests currently take about an hour and fifteen minutes
+        # to run. Split them into roughly 5-minute shards.
+        'shards': 20,
+      },
+    },
+  },
+
+  'gpu_fyi_only_win_nvidia_release_specific_telemetry_tests': {
+    'webgl2_conformance_d3d11_passthrough_tests': {
+      'args': [
+        '--webgl-conformance-version=2.0.1',
+        # The current working directory when run via isolate is
+        # out/Debug or out/Release. Reference this file relatively to
+        # it.
+        '--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json',
+        '--extra-browser-args=--use-angle=d3d11 --use-cmd-decoder=passthrough',
+      ],
+      'telemetry_test_name': 'webgl_conformance',
+      'swarming': {
+        # These tests currently take about an hour and fifteen minutes
+        # to run. Split them into roughly 5-minute shards.
+        'shards': 20,
+      },
+    },
+  },
+
+  'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests': {
+    'webgl2_conformance_tests': {
+      'telemetry_test_name': 'webgl_conformance',
+      'args': [
+        '--webgl-conformance-version=2.0.1',
+        # The current working directory when run via isolate is
+        # out/Debug or out/Release. Reference this file relatively to
+        # it.
+        '--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json',
+        '--extra-browser-args=--use-cmd-decoder=validating',
+      ],
+      'swarming': {
+        # These tests currently take about an hour and fifteen minutes
+        # to run. Split them into roughly 5-minute shards.
+        'shards': 20,
+      },
+    },
+  },
+
+  'gpu_noop_sleep_telemetry_test': {
+    # The former GPU-specific generator script contained logic to
+    # detect whether the so-called "experimental" GPU bots, which test
+    # newer driver versions, were identical to the "stable" versions
+    # of the bots, and if so to mirror their configurations. We prefer
+    # to keep this new script simpler and to just configure this by
+    # hand in waterfalls.pyl.
+    'noop_sleep': {
+    }
+  },
+
+  'gpu_telemetry_tests': {
+    'context_lost': {},
+    'depth_capture': {},
+    'gpu_process': {
+      'name': 'gpu_process_launch_tests',
+    },
+    'hardware_accelerated_feature': {},
+    'maps': {
+      'name': 'maps_pixel_test',
+      'args': [
+        '--dont-restore-color-profile-after-test',
+        '--os-type',
+        '${os_type}',
+        '--build-revision',
+        '${got_revision}',
+        '--test-machine-name',
+        '${buildername}',
+      ],
+    },
+    'pixel': {
+      'name': 'pixel_test',
+      'args': [
+        '--dont-restore-color-profile-after-test',
+        '--refimg-cloud-storage-bucket',
+        'chromium-gpu-archive/reference-images',
+        '--os-type',
+        '${os_type}',
+        '--build-revision',
+        '${got_revision}',
+        '--test-machine-name',
+        '${buildername}',
+      ],
+      'non_precommit_args': [
+        '--upload-refimg-to-cloud-storage',
+      ],
+      'precommit_args': [
+        '--download-refimg-from-cloud-storage',
+      ],
+    },
+    'screenshot_sync': {
+      'args': [
+        '--dont-restore-color-profile-after-test',
+      ],
+    },
+    'trace_test': {},
+    'webgl_conformance': {
+      'android_args': [
+        # The current working directory when run via isolate is
+        # out/Debug or out/Release. Reference this file relatively to
+        # it.
+        '--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json',
+      ],
+      'args': [
+        '--extra-browser-args=--use-cmd-decoder=validating',
+      ],
+      'swarming': {
+        'shards': 2,
+      },
+      'android_swarming': {
+        'shards': 6,
+      },
+    },
+  },
+
+  'gpu_win_specific_gtests': {
+    'gl_tests_passthrough': {
+      'test': 'gl_tests',
+      'desktop_args': [
+        '--use-gpu-in-tests',
+        '--use-cmd-decoder=passthrough',
+      ]
+    },
+    'video_decode_accelerator_d3d11_unittest': {
+      'args': [
+        '--use-angle=d3d11',
+        '--use-test-data-path',
+        '--test_video_data=test-25fps.h264:320:240:250:258:::1',
+      ],
+      'test': 'video_decode_accelerator_unittest',
+    },
+  },
+
+  # END tests which run on the GPU bots
+
   'headless_linux_gtests': {
     'headless_browsertests': {},
     'headless_unittests': {},
@@ -975,32 +1586,6 @@
     },
   },
 
-  'goma_gtests': {
-    'base_unittests': {},
-    'content_unittests': {},
-  },
-
-  'goma_mac_gtests': {
-    'base_unittests': {
-      'swarming': {
-        'dimension_sets': [
-          {
-            'os': 'Mac',
-          },
-        ],
-      }
-    },
-    'content_unittests': {
-      'swarming': {
-        'dimension_sets': [
-          {
-            'os': 'Mac',
-          },
-        ],
-      },
-    },
-  },
-
   'linux_and_mac_specific_chromium_gtests': {
     'snapshot_unittests': {},
   },
@@ -2037,6 +2622,277 @@
     'win_specific_chromium_gtests',
   ],
 
+  # BEGIN composition test suites used by the GPU bots
+
+  'gpu_angle_deqp_android_gtests': [
+    'gpu_angle_deqp_egl_gles_tests',
+    'gpu_angle_deqp_gles_gtests',
+  ],
+
+  'gpu_angle_deqp_linux_nvidia_gtests': [
+    'gpu_angle_deqp_egl_gl_tests',
+    'gpu_angle_deqp_gles2_gl_tests',
+    'gpu_angle_deqp_gles2_vulkan_tests',
+    'gpu_angle_deqp_gles3_gl_tests',
+    'gpu_angle_deqp_gles31_gl_tests',
+  ],
+
+  'gpu_angle_deqp_mac_gtests': [
+    'gpu_angle_deqp_egl_gl_tests',
+    'gpu_angle_deqp_gles2_gl_tests',
+    'gpu_angle_deqp_gles3_gl_tests',
+  ],
+
+  'gpu_angle_deqp_win_amd_gtests': [
+    'gpu_angle_deqp_gles2_d3d11_tests',
+    'gpu_angle_deqp_gles2_vulkan_tests',
+  ],
+
+  'gpu_angle_deqp_win_nvidia_gtests': [
+    'gpu_angle_deqp_egl_d3d11_tests',
+    'gpu_angle_deqp_egl_gl_tests',
+    'gpu_angle_deqp_gles2_d3d11_tests',
+    # TODO(kbr): why is gpu_angle_deqp_gles2_gl_tests not here?
+    'gpu_angle_deqp_gles2_vulkan_tests',
+    'gpu_angle_deqp_gles31_d3d11_tests',
+    'gpu_angle_deqp_gles31_gl_tests',
+    'gpu_angle_deqp_gles3_d3d11_tests',
+    # TODO(kbr): why is gpu_angle_deqp_gles3_gl_tests not here?
+  ],
+
+  'gpu_angle_gtests': [
+    'gpu_angle_unittests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+  ],
+
+  'gpu_common_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_common_win_and_linux_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_desktop_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_desktop_specific_gtests',
+  ],
+
+  'gpu_fyi_and_optional_win_isolated_scripts': [
+    'gpu_angle_fyi_and_optional_win_specific_isolated_scripts',
+    'gpu_fyi_and_optional_isolated_scripts',
+  ],
+
+  'gpu_fyi_android_and_mac_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+  ],
+
+  'gpu_fyi_linux_debug_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+  ],
+
+  'gpu_fyi_linux_optional_gtests': [
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+  ],
+
+  'gpu_fyi_linux_release_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_desktop_specific_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+  ],
+
+  'gpu_fyi_mac_debug_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+    'gpu_fyi_mac_specific_gtests',
+  ],
+
+  'gpu_fyi_mac_optional_gtests': [
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+    'gpu_fyi_mac_specific_gtests',
+  ],
+
+  'gpu_fyi_mac_release_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_desktop_specific_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+    'gpu_fyi_mac_specific_gtests',
+  ],
+
+  'gpu_fyi_mac_release_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_optional_linux_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+  ],
+
+  'gpu_fyi_optional_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_and_linux_intel_and_nvidia_release_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_only_win_linux_intel_nvidia_specific_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_and_linux_release_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_and_linux_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_debug_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_default_and_optional_win_specific_gtests',
+    'gpu_desktop_specific_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_and_optional_win_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+    'gpu_win_specific_gtests',
+  ],
+
+  'gpu_fyi_win_nvidia_release_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_only_win_linux_intel_nvidia_specific_telemetry_tests',
+    'gpu_fyi_only_win_nvidia_release_specific_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win_optional_gtests': [
+    'gpu_default_and_optional_win_specific_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_and_optional_win_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+  ],
+
+  'gpu_fyi_win_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_fyi_win7_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_desktop_specific_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+    'gpu_fyi_and_optional_non_linux_gtests',
+    'gpu_fyi_and_optional_win_specific_gtests',
+    'gpu_fyi_desktop_specific_gtests',
+    'gpu_win_specific_gtests',
+  ],
+
+  'gpu_fyi_win7_nvidia_release_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_default_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_optional_win_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_and_optional_win_and_linux_specific_telemetry_tests',
+    'gpu_fyi_and_optional_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+  ],
+
+  'gpu_v8_desktop_telemetry_tests': [
+    'gpu_common_and_optional_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+    'gpu_telemetry_tests',
+  ],
+
+  'gpu_win_angle_amd_gtests': [
+    'gpu_fyi_and_optional_and_win_angle_amd_gtests',
+    'gpu_fyi_and_optional_and_win_angle_amd_win_and_linux_specific_gtests',
+  ],
+
+  'gpu_win_angle_amd_telemetry_tests': [
+    'gpu_fyi_and_optional_and_win_angle_amd_win_specific_gpu_telemetry_tests',
+    'gpu_fyi_optional_v8_and_win_angle_amd_desktop_release_specific_telemetry_tests',
+  ],
+
+  'gpu_win_gtests': [
+    'gpu_angle_unittests',
+    'gpu_common_gtests',
+    'gpu_default_and_optional_win_specific_gtests',
+    'gpu_desktop_specific_gtests',
+    'gpu_win_specific_gtests',
+  ],
+
+  # END composition test suites used by the GPU bots
+
   'marshmallow_isolated_scripts': [
     'components_perftests_isolated_scripts',
     'monochrome_apk_checker_isolated_script',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index b692fc5..54cc8ad 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -831,6 +831,1178 @@
     },
   },
   {
+    'name': 'chromium.gpu',
+    'machines': {
+      'Android Release (Nexus 5X)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_os': 'MMB29Q',
+              'device_type': 'bullhead',
+              'os': 'Android',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'GPU Linux Builder': {},
+      'GPU Linux Builder (dbg)': {},
+      'GPU Mac Builder': {},
+      'GPU Mac Builder (dbg)': {},
+      'GPU Win Builder': {},
+      'GPU Win Builder (dbg)': {},
+      'Linux Debug (NVIDIA)': {
+        'browser_config': 'debug',
+        'os_type': 'linux',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
+        },
+      },
+      'Linux Release (NVIDIA)': {
+        'browser_config': 'release',
+        'os_type': 'linux',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
+        },
+      },
+      'Mac Debug (Intel)': {
+        'browser_config': 'debug',
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac Release (Intel)': {
+        'browser_config': 'release',
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac Retina Debug (AMD)': {
+        'browser_config': 'debug',
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac Retina Release (AMD)': {
+        'browser_config': 'release',
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_desktop_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Win10 Debug (NVIDIA)': {
+        'browser_config': 'debug',
+        'os_type': 'win',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_win_gtests',
+          'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
+        },
+      },
+      'Win10 Release (NVIDIA)': {
+        'browser_config': 'release',
+        'os_type': 'win',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_win_gtests',
+          'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
+        },
+        'use_multi_dimension_trigger_script': True,
+      },
+    },
+  },
+  {
+    'name': 'chromium.gpu.fyi',
+    'machines': {
+      'Android FYI 32 Vk Release (Nexus 5X)': {
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'O',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+        },
+      },
+      'Android FYI 32 dEQP Vk Release (Nexus 5X)': {
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'O',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_gles2_vulkan_tests',
+        },
+      },
+      'Android FYI 64 Vk Release (Nexus 5X)': {
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'O',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+        },
+      },
+      'Android FYI 64 dEQP Vk Release (Nexus 5X)': {
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'O',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_gles2_vulkan_tests',
+        },
+      },
+      'Android FYI Release (NVIDIA Shield TV)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'foster',
+              'device_os': 'N',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI Release (Nexus 5)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'hammerhead',
+              'device_os': 'L',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI Release (Nexus 5X)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'MMB29Q',
+              'os': 'Android'
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI Release (Nexus 6)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'shamu',
+              'device_os': 'L',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI Release (Nexus 6P)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'angler',
+              'device_os': 'M',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI Release (Nexus 9)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'flounder',
+              'device_os': 'M',
+              'os': 'Android',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_android_and_mac_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Android FYI dEQP Release (Nexus 5X)': {
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'MMB29Q',
+              'os': 'Android'
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_android_gtests',
+        },
+      },
+      'GPU FYI Linux Builder' : {},
+      'GPU FYI Linux Builder (dbg)' : {},
+      'GPU FYI Linux Ozone Builder' : {},
+      'GPU FYI Linux dEQP Builder' : {},
+      'GPU FYI Mac Builder' : {},
+      'GPU FYI Mac Builder (dbg)' : {},
+      'GPU FYI Mac dEQP Builder' : {},
+      'GPU FYI Win Builder' : {},
+      'GPU FYI Win Builder (dbg)' : {},
+      'GPU FYI Win dEQP Builder': {},
+      'GPU FYI Win x64 Builder' : {},
+      'GPU FYI Win x64 Builder (dbg)' : {},
+      'GPU FYI Win x64 dEQP Builder' : {},
+      'Linux FYI Debug (NVIDIA)': {
+        'browser_config': 'debug',
+        'os_type': 'linux',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_debug_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_and_linux_telemetry_tests',
+        },
+      },
+      'Linux FYI GPU TSAN Release': {
+        # This bot doesn't run any Telemetry-based tests so doesn't
+        # need the browser_config parameter.
+        'os_type': 'linux',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          # This bot doesn't run any browser-based tests
+          # (tab_capture_end2end_tests) either.
+          'gtest_tests': 'gpu_fyi_linux_debug_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+        },
+      },
+      'Linux FYI Ozone (Intel)': {
+        'os_type': 'linux',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:1912',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        # This bot runs only a few tests so far.
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_gtests',
+        },
+      },
+      'Linux FYI Release (AMD R7 240)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6613',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+          # There's only one bot of this type in the Swarming pool right now, so
+          # we have to increase the default expiration time of 1 hour (3600
+          # seconds) to prevent webgl2_conformance_tests' shards from timing
+          # out.
+          'expiration': 10800,
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_and_linux_release_telemetry_tests',
+        },
+      },
+      'Linux FYI Release (Intel HD 630)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # INTEL_HD_630
+              'gpu': '8086:5912',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_release_gtests',
+          # TODO(kbr): switch this to
+          # gpu_fyi_win_and_linux_intel_and_nvidia_release_telemetry_tests. Seems
+          # to have been an oversight that
+          # webgl2_conformance_gl_passthrough_tests was't running on the Linux
+          # Intel HD 630 bots.
+          'gpu_telemetry_tests': 'gpu_fyi_win_and_linux_release_telemetry_tests',
+        },
+      },
+      'Linux FYI Release (NVIDIA)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_release_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_fyi_win_and_linux_intel_and_nvidia_release_telemetry_tests',
+        },
+      },
+      'Linux FYI dEQP Release (Intel HD 630)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # INTEL_HD_630
+              'gpu': '8086:5912',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_gles2_gl_tests',
+        },
+      },
+      'Linux FYI dEQP Release (NVIDIA)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_linux_nvidia_gtests',
+        },
+      },
+      'Mac FYI Debug (Intel)': {
+        'os_type': 'mac',
+        'browser_config': 'debug',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_debug_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac FYI Experimental Release (Intel)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.13.4',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI Experimental Retina Release (AMD)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.13.4',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI Experimental Retina Release (NVIDIA)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '10de:0fe9',
+              'hidpi': '1',
+              'os': 'Mac-10.13.4',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+          # There's only one bot of this type in the Swarming pool right now, so
+          # we have to increase the default expiration time of 1 hour (3600
+          # seconds) to prevent webgl2_conformance_tests' shards from timing
+          # out.
+          'expiration': 10800,
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI GPU ASAN Release': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            # This bot spawns jobs on multiple GPU types.
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI Release (Intel)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI Retina Debug (AMD)': {
+        'os_type': 'mac',
+        'browser_config': 'debug',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_debug_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac FYI Retina Debug (NVIDIA)': {
+        'os_type': 'mac',
+        'browser_config': 'debug',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '10de:0fe9',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_debug_gtests',
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Mac FYI Retina Release (AMD)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI Retina Release (NVIDIA)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '10de:0fe9',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Mac FYI dEQP Release AMD': {
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_mac_gtests',
+        },
+      },
+      'Mac FYI dEQP Release Intel': {
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_mac_gtests',
+        },
+      },
+      'Mac Pro FYI Release (AMD)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:679e',
+              'os': 'Mac-10.12',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+          # There are only two bots of this type in the Swarming pool right now,
+          # so we have to increase the default expiration time of 1 hour (3600
+          # seconds) to prevent webgl2_conformance_tests' shards from timing
+          # out.
+          'expiration': 10800,
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_release_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_mac_release_telemetry_tests',
+        },
+      },
+      'Optional Android Release (Nexus 5X)': {
+        'os_type': 'android',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+              'device_os': 'MMB29Q',
+              'os': 'Android'
+            },
+          ],
+        },
+        # No tests run on this bot right now; they are all running on the main
+        # android_n5x_swarming_rel bot. We should get the WebGL 2.0 conformance
+        # tests running on this bot, similarly to how they run on the optional
+        # desktop trybots.
+      },
+      'Optional Linux Release (Intel HD 630)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # INTEL_HD_630
+              'gpu': '8086:5912',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_optional_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_optional_linux_telemetry_tests',
+        },
+      },
+      'Optional Linux Release (NVIDIA)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_linux_optional_gtests',
+          # This optional tryserver additionally runs angle_perftests.
+          'isolated_scripts': 'gpu_fyi_and_optional_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_fyi_optional_linux_telemetry_tests',
+        },
+      },
+      'Optional Mac Release (Intel)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_optional_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_optional_telemetry_tests',
+        },
+      },
+      'Optional Mac Retina Release (AMD)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6821',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_optional_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_optional_telemetry_tests',
+        },
+      },
+      'Optional Mac Retina Release (NVIDIA)': {
+        'os_type': 'mac',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '10de:0fe9',
+              'hidpi': '1',
+              'os': 'Mac-10.12.6',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_mac_optional_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_optional_telemetry_tests',
+        },
+      },
+      'Optional Win10 Release (Intel HD 630)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_INTEL_HD_630_STABLE_DRIVER
+              'gpu': '8086:5912-23.20.16.4877',
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_optional_gtests',
+          'gpu_telemetry_tests': 'gpu_optional_win_telemetry_tests',
+        },
+      },
+      'Optional Win10 Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_optional_gtests',
+          # This optional tryserver additionally runs angle_perftests.
+          'isolated_scripts': 'gpu_fyi_and_optional_win_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_optional_win_telemetry_tests',
+        },
+        'use_multi_dimension_trigger_script': True,
+      },
+      'Win10 FYI Debug (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'debug',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_debug_telemetry_tests',
+        },
+      },
+      'Win10 FYI Exp Release (Intel HD 630)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_INTEL_HD_630_EXPERIMENTAL_DRIVER
+              'gpu': '8086:5912-24.20.100.6025',
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_telemetry_tests',
+        },
+      },
+      # TODO(kbr): "Experimental" caused too-long path names pre-LUCI.
+      # crbug.com/812000
+      'Win10 FYI Exp Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_EXPERIMENTAL_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        # Currently the experimental driver is identical to the stable
+        # driver. If it's upgraded, change these test_suites to be the same as
+        # 'Win10 FYI Release (NVIDIA)'.
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_noop_sleep_telemetry_test',
+        },
+      },
+      'Win10 FYI Release (Intel HD 630)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_INTEL_HD_630_STABLE_DRIVER
+              'gpu': '8086:5912-23.20.16.4877',
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_telemetry_tests',
+        },
+      },
+      'Win10 FYI Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'isolated_scripts': 'gpu_fyi_and_optional_win_isolated_scripts',
+          'gpu_telemetry_tests': 'gpu_fyi_win_nvidia_release_telemetry_tests',
+        },
+        'use_multi_dimension_trigger_script': True,
+      },
+      'Win10 FYI dEQP Release (Intel HD 630)': {
+        'os_type': 'win',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_INTEL_HD_630_STABLE_DRIVER
+              'gpu': '8086:5912-23.20.16.4877',
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_gles2_d3d11_tests',
+        },
+      },
+      'Win10 FYI dEQP Release (NVIDIA)': {
+        'os_type': 'win',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8816',
+              # WIN10_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-10',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_win_nvidia_gtests',
+        },
+      },
+      # This tryserver doesn't actually exist; it's a separate
+      # configuration from the Win AMD bot on this waterfall because we
+      # don't have enough tryserver capacity to run all the tests from
+      # that bot on win_angle_rel_ng.
+      'Win7 ANGLE Tryserver (AMD)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6613',
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_win_angle_amd_gtests',
+          'gpu_telemetry_tests': 'gpu_win_angle_amd_telemetry_tests',
+        },
+      },
+      'Win7 FYI Debug (AMD)': {
+        'os_type': 'win',
+        'browser_config': 'debug',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6613',
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_debug_telemetry_tests',
+        },
+      },
+      'Win7 FYI Release (AMD)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6613',
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_telemetry_tests',
+        },
+      },
+      'Win7 FYI Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8792',
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win7_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win7_nvidia_release_telemetry_tests',
+        },
+        'use_multi_dimension_trigger_script': True,
+      },
+      'Win7 FYI dEQP Release (AMD)': {
+        'os_type': 'win',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '1002:6613',
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_angle_deqp_win_amd_gtests',
+        },
+      },
+      'Win7 FYI x64 Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release_x64',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8792',
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'gpu_fyi_win7_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win7_nvidia_release_telemetry_tests',
+        },
+      },
+      'Win7 FYI x64 dEQP Release (NVIDIA)': {}
+    },
+  },
+  {
     'name': 'chromium.fyi',
     'machines': {
       'Android Builder (dbg) Goma Canary': {
@@ -1028,9 +2200,6 @@
         'test_suites': {
           'isolated_scripts': 'webkit_layout_tests_isolated_scripts',
         },
-        'args': [
-          '--additional-driver-flag=--enable-blink-gen-property-trees',
-        ],
       },
       'linux-blink-heap-verification': {
         'test_suites': {
@@ -2075,6 +3244,153 @@
     'machines': {},
   },
   {
+    'name': 'client.v8.fyi',
+    'machines': {
+      'Android V8 FYI Release (Nexus 5X)': {
+        'browser_config': 'android-chromium',
+        'os_type': 'android',
+        'skip_merge_script': True,
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_os': 'MMB29Q',
+              'device_type': 'bullhead',
+              'os': 'Android',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_common_telemetry_tests',
+        },
+      },
+      'Linux V8 FYI Release (NVIDIA)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
+        },
+      },
+      'Linux V8 FYI Release - concurrent marking (NVIDIA)': {
+        'os_type': 'linux',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # LINUX_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-384.90',
+              'os': 'Ubuntu',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
+        },
+      },
+      'Mac V8 FYI Release (Intel)': {
+        'browser_config': 'release',
+        'os_type': 'mac',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'gpu': '8086:0a2e',
+              'os': 'Mac-10.12.6',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
+        },
+      },
+      'V8 Android GN (dbg)': {
+        'additional_compile_targets': [
+          'chrome_public_apk'
+        ],
+      },
+      'V8 Linux GN': {
+        'additional_compile_targets': [
+          'accessibility_unittests',
+          'aura_unittests',
+          'browser_tests',
+          'cacheinvalidation_unittests',
+          'capture_unittests',
+          'cast_unittests',
+          'cc_unittests',
+          'chromedriver_unittests',
+          'components_browsertests',
+          'components_unittests',
+          'content_browsertests',
+          'content_unittests',
+          'crypto_unittests',
+          'dbus_unittests',
+          'device_unittests',
+          'display_unittests',
+          'events_unittests',
+          'extensions_browsertests',
+          'extensions_unittests',
+          'gcm_unit_tests',
+          'gfx_unittests',
+          'gn_unittests',
+          'google_apis_unittests',
+          'gpu_unittests',
+          'interactive_ui_tests',
+          'ipc_tests',
+          'jingle_unittests',
+          'media_unittests',
+          'media_blink_unittests',
+          'mojo_unittests',
+          'nacl_loader_unittests',
+          'net_unittests',
+          'pdf_unittests',
+          'ppapi_unittests',
+          'printing_unittests',
+          'remoting_unittests',
+          'sandbox_linux_unittests',
+          'skia_unittests',
+          'sql_unittests',
+          'storage_unittests',
+          'sync_integration_tests',
+          'ui_base_unittests',
+          'ui_touch_selection_unittests',
+          'unit_tests',
+          'url_unittests',
+          'views_unittests',
+          'wm_unittests',
+        ],
+      },
+      'Win V8 FYI Release (NVIDIA)': {
+        'os_type': 'win',
+        'browser_config': 'release',
+        'swarming': {
+          'dimension_sets': [
+            {
+              # TODO(kbr): cut this bot over to Win10, coordinating with
+              # V8 team.
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_DRIVER
+              'gpu': '10de:1cb3-23.21.13.8792',
+              # WIN7_NVIDIA_QUADRO_P400_STABLE_OS
+              'os': 'Windows-2008ServerR2-SP1',
+              'pool': 'Chrome-GPU',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
+        },
+      },
+    },
+  },
+  {
     'name': 'tryserver.webrtc',
     'machines': {
       'win_chromium_compile': {
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index 54f39e1..fd8e86d 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -125,7 +125,7 @@
 crbug.com/591099 compositing/overflow/scroll-ancestor-update.html [ Failure ]
 crbug.com/591099 compositing/self-painting-layers.html [ Failure ]
 crbug.com/591099 compositing/squashing/add-remove-squashed-layers.html [ Failure ]
-crbug.com/591099 crypto/subtle/hmac/cloneKey.html [ Timeout ]
+crbug.com/591099 crypto/subtle/hmac/cloneKey.html [ Pass Timeout ]
 crbug.com/591099 css1/box_properties/float_on_text_elements.html [ Failure ]
 crbug.com/591099 css1/classification/list_style_image.html [ Failure ]
 crbug.com/591099 css2.1/20110323/margin-applies-to-008.htm [ Failure ]
@@ -243,6 +243,9 @@
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-011.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-ic-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-ic-003.html [ Pass ]
+crbug.com/591099 external/wpt/css/css-text/text-transform/text-transform-capitalize-018.html [ Crash ]
+crbug.com/591099 external/wpt/css/css-text/text-transform/text-transform-upperlower-018.html [ Crash ]
+crbug.com/591099 external/wpt/css/css-text/text-transform/text-transform-upperlower-019.html [ Crash ]
 crbug.com/591099 external/wpt/css/css-text/white-space/pre-wrap-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/white-space/seg-break-transformation-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/white-space/seg-break-transformation-011.html [ Failure ]
@@ -251,6 +254,7 @@
 crbug.com/591099 external/wpt/css/css-transforms/transform-transformed-tr-percent-height-child.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-transforms/transform3d-perspective-008.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-010.html [ Pass ]
+crbug.com/591099 external/wpt/css/css-ui/text-overflow-012.html [ Crash ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/abs-pos-non-replaced-icb-vlr-003.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/abs-pos-non-replaced-icb-vlr-005.xht [ Pass ]
@@ -351,6 +355,8 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/text-baseline-vlr-007.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-combine-upright-decorations-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-combine-upright-layout-rules-001.html [ Failure ]
+crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-script-001a.html [ Crash ]
+crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-script-001c.html [ Crash ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-005.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-007.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-vlr-023.xht [ Failure ]
@@ -384,6 +390,9 @@
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-padding-box-border-radius-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-break-inside-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-007.html [ Failure ]
+crbug.com/591099 external/wpt/dom/nodes/DOMImplementation-createDocument.html [ Crash ]
+crbug.com/591099 external/wpt/dom/nodes/Document-createElement.html [ Crash ]
+crbug.com/591099 external/wpt/dom/nodes/Document-createElementNS.html [ Crash ]
 crbug.com/591099 external/wpt/dom/ranges/Range-compareBoundaryPoints.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/ranges/Range-comparePoint.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/ranges/Range-isPointInRange.html [ Timeout ]
@@ -395,6 +404,33 @@
 crbug.com/591099 external/wpt/editing/run/justifyfull.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/justifyright.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/multitest.html [ Pass ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-form-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-form-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-form-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-href-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-href-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-encode-href-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-href-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-form-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-form-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-form-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-href-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-href-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-href-errors-misc.html?2001-3000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-errors-misc.html?1-1000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-errors-misc.html?1001-2000 [ Crash ]
+crbug.com/591099 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-errors-misc.html?2001-3000 [ Crash ]
 crbug.com/591099 external/wpt/encoding/textdecoder-fatal-single-byte.html [ Timeout ]
 crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Pass ]
 crbug.com/591099 external/wpt/html-media-capture/capture_audio_cancel-manual.html [ Failure ]
@@ -409,6 +445,7 @@
 crbug.com/591099 external/wpt/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/interactive-elements/the-dialog-element/centering.html [ Crash ]
+crbug.com/591099 external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.html [ Pass ]
 crbug.com/591099 external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html [ Pass ]
 crbug.com/591099 external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html [ Pass ]
 crbug.com/591099 external/wpt/pointerevents/pointerevent_click_during_capture-manual.html [ Crash Timeout ]
@@ -552,9 +589,16 @@
 crbug.com/591099 fast/doctypes/001.html [ Failure ]
 crbug.com/591099 fast/doctypes/003.html [ Failure ]
 crbug.com/591099 fast/doctypes/004.html [ Failure ]
+crbug.com/591099 fast/dom/DOMImplementation/createDocument-namespace-err.html [ Crash ]
+crbug.com/591099 fast/dom/Document/createAttributeNS-namespace-err.html [ Crash ]
+crbug.com/591099 fast/dom/Document/createElement-invalid-names.html [ Crash ]
+crbug.com/591099 fast/dom/Document/createElement-valid-names.html [ Crash ]
+crbug.com/591099 fast/dom/Document/createElementNS-namespace-err.html [ Crash ]
+crbug.com/591099 fast/dom/Document/createProcessingInstruction-invalid-target.html [ Crash ]
 crbug.com/714962 fast/dom/Element/client-rect-list-argument.html [ Failure ]
 crbug.com/591099 fast/dom/Element/getBoundingClientRect.html [ Failure ]
 crbug.com/714962 fast/dom/Element/getClientRects.html [ Failure ]
+crbug.com/591099 fast/dom/Element/setAttributeNS-namespace-err.html [ Crash ]
 crbug.com/591099 fast/dom/HTMLAreaElement/area-download.html [ Failure ]
 crbug.com/714962 fast/dom/Range/getBoundingClientRect-linebreak-character.html [ Failure ]
 crbug.com/591099 fast/dom/Window/window-lookup-precedence.html [ Failure ]
@@ -747,10 +791,15 @@
 crbug.com/591099 fast/text/emphasis-complex.html [ Failure ]
 crbug.com/591099 fast/text/emphasis-ellipsis-complextext.html [ Failure ]
 crbug.com/591099 fast/text/emphasis-overlap.html [ Failure ]
+crbug.com/591099 fast/text/font-features/caps-casemapping.html [ Crash ]
 crbug.com/591099 fast/text/hide-atomic-inlines-after-ellipsis.html [ Failure ]
+crbug.com/591099 fast/text/international/harfbuzz-buffer-overrun.html [ Crash ]
 crbug.com/714962 fast/text/international/hindi-whitespace.html [ Failure ]
 crbug.com/796943 fast/text/international/shape-across-elements-simple.html [ Pass ]
 crbug.com/591099 fast/text/international/text-combine-image-test.html [ Failure ]
+crbug.com/591099 fast/text/international/thai-line-breaks.html [ Crash ]
+crbug.com/591099 fast/text/international/zerowidthjoiner.html [ Crash ]
+crbug.com/591099 fast/text/multiglyph-characters.html [ Crash ]
 crbug.com/591099 fast/text/orientation-sideways.html [ Failure ]
 crbug.com/591099 fast/text/place-ellipsis-in-inline-block-adjacent-float-2.html [ Failure ]
 crbug.com/591099 fast/text/place-ellipsis-in-inline-block-adjacent-float.html [ Failure ]
@@ -788,7 +837,6 @@
 crbug.com/591099 fast/writing-mode/vertical-baseline-alignment.html [ Failure ]
 crbug.com/591099 fast/writing-mode/vertical-font-fallback.html [ Failure ]
 crbug.com/591099 fast/writing-mode/vertical-lr-replaced-selection.html [ Failure ]
-crbug.com/591099 fullscreen/full-screen-line-boxes-crash.html [ Failure Pass ]
 crbug.com/591099 fullscreen/full-screen-with-flex-item.html [ Failure ]
 crbug.com/591099 hittesting/border-hittest-inlineFlowBox.html [ Failure ]
 crbug.com/714962 hittesting/culled-inline.html [ Failure ]
@@ -806,8 +854,7 @@
 crbug.com/591099 http/tests/csspaint/invalidation-content-image.html [ Timeout ]
 crbug.com/591099 http/tests/devtools/console/console-search.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/console/console-viewport-control.js [ Failure ]
-crbug.com/591099 http/tests/devtools/editor/text-editor-ctrl-d-2.js [ Pass Timeout ]
-crbug.com/714962 http/tests/devtools/elements/inspect-pseudo-element.js [ Timeout ]
+crbug.com/714962 http/tests/devtools/elements/inspect-pseudo-element.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/persistence/persistence-merge-editor-tabs.js [ Failure ]
 crbug.com/591099 http/tests/devtools/text-autosizing-override.js [ Failure ]
 crbug.com/591099 http/tests/devtools/tracing/timeline-misc/timeline-grouped-invalidations.js [ Failure ]
@@ -816,7 +863,7 @@
 crbug.com/591099 http/tests/devtools/tracing/timeline-paint/timeline-paint-with-layout-invalidations.js [ Failure ]
 crbug.com/591099 http/tests/devtools/tracing/timeline-paint/timeline-paint-with-style-recalc-invalidations.js [ Failure ]
 crbug.com/591099 http/tests/images/restyle-decode-error.html [ Failure ]
-crbug.com/783102 http/tests/incremental/frame-focus-before-load.html [ Timeout ]
+crbug.com/783102 http/tests/incremental/frame-focus-before-load.html [ Pass Timeout ]
 crbug.com/591099 http/tests/incremental/slow-utf8-text.pl [ Pass Timeout ]
 crbug.com/591099 http/tests/loading/nested_bad_objects.php [ Failure ]
 crbug.com/591099 http/tests/loading/preload-picture-nested.html [ Failure ]
@@ -833,7 +880,7 @@
 crbug.com/591099 http/tests/permissions/test-api-surface.html [ Pass ]
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
-crbug.com/591099 http/tests/security/cross-origin-session-storage-allowed.html [ Pass ]
+crbug.com/591099 http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Pass ]
 crbug.com/591099 http/tests/security/xssAuditor/block-does-not-leak-location.html [ Failure ]
 crbug.com/591099 http/tests/text-autosizing/narrow-iframe.html [ Failure ]
 crbug.com/591099 http/tests/text-autosizing/wide-iframe.html [ Failure ]
@@ -1089,7 +1136,7 @@
 crbug.com/591099 scrollbars/scrollbar-miss-mousemove-disabled.html [ Failure ]
 crbug.com/591099 shapedetection/detection-HTMLVideoElement.html [ Pass ]
 crbug.com/591099 storage/indexeddb/cursor-continue-validity.html [ Timeout ]
-crbug.com/591099 storage/indexeddb/index-cursor.html [ Pass Timeout ]
+crbug.com/591099 storage/indexeddb/index-cursor.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/mozilla/indexes.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/mozilla/test_objectStore_openKeyCursor.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/objectstore-cursor.html [ Pass ]
@@ -1106,6 +1153,7 @@
 crbug.com/591099 svg/in-html/sizing/svg-inline.html [ Failure ]
 crbug.com/714962 svg/text/tspan-multiple-outline.svg [ Failure ]
 crbug.com/591099 svg/transforms/text-with-pattern-inside-transformed-html.xhtml [ Failure ]
+crbug.com/591099 svg/wicd/rightsizing-grid.html [ Failure ]
 crbug.com/591099 svg/zoom/page/zoom-img-preserveAspectRatio-support-1.html [ Failure ]
 crbug.com/591099 svg/zoom/page/zoom-svg-through-object-with-absolute-size-2.xhtml [ Failure ]
 crbug.com/591099 svg/zoom/page/zoom-svg-through-object-with-absolute-size.xhtml [ Failure ]
@@ -1155,7 +1203,7 @@
 crbug.com/591099 virtual/gpu-rasterization/images/feature-policy-max-downscaling-image-resize.html [ Failure ]
 crbug.com/591099 virtual/gpu-rasterization/images/feature-policy-max-downscaling-image-styles.html [ Failure ]
 crbug.com/591099 virtual/gpu-rasterization/images/feature-policy-max-downscaling-image.html [ Failure ]
-crbug.com/591099 virtual/gpu/fast/canvas/shadow-huge-blur.html [ Pass Timeout ]
+crbug.com/591099 virtual/gpu/fast/canvas/shadow-huge-blur.html [ Pass ]
 crbug.com/591099 virtual/layout_ng/ [ Skip ]
 crbug.com/824918 virtual/layout_ng_experimental/ [ Skip ]
 crbug.com/714962 virtual/mouseevent_fractional/fast/events/event-on-culled_inline.html [ Failure ]
@@ -1167,7 +1215,7 @@
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/wheel/mainthread-touchpad-fling-latching.html [ Pass ]
 crbug.com/591099 virtual/mouseevent_fractional/fast/events/wheel/wheel-scroll-latching-on-scrollbar.html [ Pass ]
 crbug.com/591099 virtual/new-remote-playback-pipeline/ [ Skip ]
-crbug.com/591099 virtual/off-main-thread-websocket/http/tests/websocket/invalid-subprotocol-characters.html [ Pass Timeout ]
+crbug.com/591099 virtual/off-main-thread-websocket/http/tests/websocket/invalid-subprotocol-characters.html [ Timeout ]
 crbug.com/591099 virtual/outofblink-cors/ [ Skip ]
 crbug.com/591099 virtual/paint-timing/external/wpt/paint-timing/sibling-painting-first-image.html [ Failure ]
 crbug.com/591099 virtual/prefer_compositing_to_lcd_text/ [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index e71765d..e15c5de 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1898,9 +1898,6 @@
 crbug.com/492664 [ Win ] external/wpt/css/css-writing-modes/bidi-override-005.html [ Failure ]
 crbug.com/492664 [ Win ] external/wpt/css/css-writing-modes/bidi-plaintext-001.html [ Failure ]
 
-crbug.com/267206 fast/scrolling/scrollbar-tickmarks-hittest.html [ Timeout ]
-crbug.com/267206 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbar-tickmarks-hittest.html [ Timeout ]
-
 crbug.com/280342 [ Mac Linux Win ] http/tests/media/progress-events-generated-correctly.html [ Failure Pass ]
 
 crbug.com/520736 [ Win7 ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
@@ -4114,6 +4111,7 @@
 crbug.com/832447 virtual/new-remote-playback-pipeline/media/controls/controls-page-zoom-out.html [ Failure Pass ]
 crbug.com/832447 virtual/video-surface-layer/media/controls/controls-page-zoom-in.html [ Failure Pass ]
 crbug.com/832447 virtual/video-surface-layer/media/controls/controls-page-zoom-out.html [ Failure Pass ]
+crbug.com/848919 [ Win ] virtual/video-surface-layer/media/controls/paint-controls-webkit-appearance-none-custom-bg.html [ Failure Pass ]
 
 # Tests failing on Android after https://chromium-review.googlesource.com/612442
 crbug.com/755405 [ Android ] compositing/rendering-contexts.html [ Failure ]
@@ -4827,3 +4825,8 @@
 crbug.com/848851 virtual/exotic-color-space/images/image-hover-display-alt.html [ Failure Pass ]
 crbug.com/848799 [ Mac ] http/tests/devtools/coverage/multiple-instances-merge.js [ Pass Timeout ]
 crbug.com/828962 [ Mac ] fast/webgl/webgl-composite-modes-tabswitching.html [ Pass Timeout ]
+
+# Flakes 2018-06-02
+crbug.com/848006 [ Mac ] virtual/threaded/animations/responsive/zoom-responsive-translate-animation.html [ Crash Failure Pass ]
+crbug.com/849040 [ Mac ] virtual/threaded/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js [ Crash Timeout Pass ]
+crbug.com/841567 fast/scrolling/scrollbar-tickmarks-hittest.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/resources/test_page.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/resources/test_page.html
deleted file mode 100644
index 8561834..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/resources/test_page.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<!doctype html>
-<title> An empty test page </title>
-<p> This is test page </p>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-embed-element/detach-frame-on-src-change.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-embed-element/detach-frame-on-src-change.html
deleted file mode 100644
index 1e8f018..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-embed-element/detach-frame-on-src-change.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <title>
-    &lt;embed&gt;'s browsing context is discarded on 'src' attribute change.
-  </title>
-  <link rel="author" title="Ehsan Karamad" href="ekaramad@chromium.org">
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-  <script>
-    let url1 = "../resources/test_page.html";
-    let url2 = "../resources/should-load.html";
-    function onLoadPromise(el) {
-      return new Promise((resolve) => {
-        function onLoad() {
-          resolve();
-          el.removeEventListener("load", onLoad);
-        }
-        el.addEventListener("load", onLoad);
-      });
-    }
-
-    promise_test(async() => {
-      let old_windows = [];
-
-      let embed = document.createElement("embed");
-      embed.type = "text/html";
-      embed.src = url1;
-      let onEmbedLoad = onLoadPromise(embed);
-      document.body.appendChild(embed);
-      await onEmbedLoad;
-      old_windows.push(window[0]);
-      assert_equals(
-        window[0].frameElement,
-        embed,
-        "<embed> is attached and loaded with html content.");
-
-      let iframe = document.createElement("iframe");
-      iframe.src = url1;
-      let onIframeLoad = onLoadPromise(iframe);
-      document.body.appendChild(iframe);
-      await onIframeLoad;
-      old_windows.push(window[1]);
-      assert_equals(
-        window[1].frameElement,
-        iframe,
-        "<iframe> is attached and loaded with html content after <embed>.");
-      assert_equals(
-        window[0],
-        old_windows[0],
-        "The first window is that of <embed>'s frame.");
-
-      // Now navigate the embed element again.
-      onEmbedLoad = onLoadPromise(embed);
-      embed.src = url2;
-      await onEmbedLoad;
-      assert_equals(
-        window[0].frameElement,
-        iframe,
-        "<embed>'s previous frame must have been destroyed.");
-
-      assert_equals(
-        window[1].frameElement,
-        embed,
-        "<embed>'s new window should be appended after navigation.");
-
-      assert_not_equals(
-        old_windows[0],
-        window[1],
-        "The old window and new window are different for <embed>.");
-
-      assert_equals(
-        old_windows[1],
-        window[0],
-        "The old and new window are the same for <iframe>.");
-    }, "Verify that changing 'src' attribute of an <embed> element discards" +
-       " the old browsing context and creates a new browsing context.");
-  </script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-object-element/detach-frame-on-data-change.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-object-element/detach-frame-on-data-change.html
deleted file mode 100644
index 6d114c4..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/the-object-element/detach-frame-on-data-change.html
+++ /dev/null
@@ -1,83 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <title>
-    &lt;object&gt;'s browsing context is discarded on 'data' attribute change.
-  </title>
-  <link rel="author" title="Ehsan Karamad" href="ekaramad@chromium.org">
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-</head>
-<body>
-  <script>
-    let url1 = "../resources/test_page.html";
-    let url2 = "../resources/should-load.html";
-    function onLoadPromise(el) {
-      return new Promise((resolve) => {
-        function onLoad() {
-          resolve();
-          el.removeEventListener("load", onLoad);
-        }
-        el.addEventListener("load", onLoad);
-      });
-    }
-
-    promise_test(async() => {
-      let old_windows = [];
-
-      let object = document.createElement("object");
-      object.type = "text/html";
-      object.data = url1;
-      let onObjectLoad = onLoadPromise(object);
-      document.body.appendChild(object);
-      await onObjectLoad;
-      old_windows.push(window[0]);
-      assert_equals(
-        window[0].frameElement,
-        object,
-        "<object> is attached and loaded with html content.");
-
-      let iframe = document.createElement("iframe");
-      iframe.src = url1;
-      let onIframeLoad = onLoadPromise(iframe);
-      document.body.appendChild(iframe);
-      await onIframeLoad;
-      old_windows.push(window[1]);
-      assert_equals(
-        window[1].frameElement,
-        iframe,
-        "<iframe> is attached and loaded with html content after <object>.");
-      assert_equals(
-        window[0],
-        old_windows[0],
-        "The first window is that of <object>'s frame.");
-
-      // Now navigate the object element again.
-      onObjectLoad = onLoadPromise(object);
-      object.data = url2;
-      await onObjectLoad;
-      assert_equals(
-        window[0].frameElement,
-        iframe,
-        "<object>'s previous frame must have been destroyed.");
-
-      assert_equals(
-        window[1].frameElement,
-        object,
-        "<object>'s new window should be appended after navigation.");
-
-      assert_not_equals(
-        old_windows[0],
-        window[1],
-        "The old window and new window are different for <object>.");
-
-      assert_equals(
-        old_windows[1],
-        window[0],
-        "The old and new window are the same for <iframe>.");
-
-    }, "Verify that changing 'data' attribute of an <object> element discards" +
-       " the old browsing context and creates a new browsing context.");
-  </script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.html
index d8480a29..6892f01 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.html
@@ -1,11 +1,14 @@
 <!doctype html>
 <meta charset="utf-8">
-<title>Test for CanMakePaymentEvent Constructor (window)</title>
+<title>Test for CanMakePaymentEvent Constructor</title>
 <link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
 <script>
+
 test(() => {
   assert_false('CanMakePaymentEvent' in window);
 }, 'CanMakePaymentEvent constructor must not be exposed in window');
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.html b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.html
new file mode 100644
index 0000000..afff850
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for CanMakePaymentEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test('can-make-payment-event-constructor.https.serviceworker.js',
+    'CanMakePaymentEvent can be constructed in service worker');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.js
new file mode 100644
index 0000000..01ce642d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.serviceworker.js
@@ -0,0 +1,45 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+  try {
+    new CanMakePaymentEvent('test', undefined);
+    new CanMakePaymentEvent('test', null);
+    new CanMakePaymentEvent('test', {});
+  } catch (err) {
+    assert_unreached(`Unexpected exception: ${err.message}`);
+  }
+}, 'CanMakePaymentEvent can be constructed in service worker.');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test', {
+    bubbles: true,
+    cancelable: true,
+    composed: true,
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_true(ev.bubbles, 'set by EventInitDict');
+  assert_true(ev.cancelable, 'set by EventInitDict');
+  assert_true(ev.composed, 'set by EventInitDict');
+  assert_equals(ev.target, null, 'initially null');
+  assert_equals(ev.type, 'test');
+}, 'CanMakePaymentEvent can be constructed with an EventInitDict, even if not trusted');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test', {
+    topOrigin: 'https://foo.com',
+    paymentRequestOrigin: 'https://bar.com',
+    methodData: [],
+    modifiers: [],
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_equals(ev.topOrigin, 'https://foo.com');
+  assert_equals(ev.paymentRequestOrigin, 'https://bar.com');
+}, 'CanMakePaymentEvent can be constructed with a CanMakePaymentEventInit, even if not trusted');
+
+test(() => {
+  const ev = new CanMakePaymentEvent('test', {});
+  self.addEventListener('test', evt => {
+    assert_equals(ev, evt);
+  });
+  self.dispatchEvent(ev);
+}, 'CanMakePaymentEvent can be dispatched, even if not trusted');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.js
index 8ae05d7..d88bddc 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/can-make-payment-event-constructor.https.worker.js
@@ -1,49 +1,7 @@
-// https://w3c.github.io/payment-handler/#the-canmakepaymentevent
-
-'use strict';
-
-if (self.importScripts) {
-  importScripts('/resources/testharness.js');
-}
+importScripts('/resources/testharness.js');
 
 test(() => {
-  try {
-    new CanMakePaymentEvent('test');
-  } catch (err) {
-    assert_unreached(`Unexpected exception: ${err.message}`);
-  }
-}, 'CanMakePaymentEvent can be constructed in service worker.');
+  assert_false('CanMakePaymentEvent' in self);
+}, 'CanMakePaymentEvent constructor must not be exposed in worker');
 
-test(() => {
-  const ev = new CanMakePaymentEvent('test', {
-    bubbles: true,
-    cancelabel: true,
-    composed: true,
-  });
-  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
-  assert_true(ev.bubbles, 'set by EventInitDict');
-  assert_true(ev.cancelable, 'set by EventInitDict');
-  assert_true(ev.composed, 'set by EventInitDict');
-  assert_equals(ev.target, null, 'initially null');
-  assert_equals(ev.type, 'test');
-}, 'CanMakePaymentEvent can be constructed with an EventInitDict, even if not trusted');
-
-test(() => {
-  const ev = new CanMakePaymentEvent('test', {
-    topOrigin: 'https://foo.com',
-    paymentRequestOrigin: 'https://bar.com',
-    methodData: [],
-    modifiers: [],
-  });
-  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
-  assert_equals(ev.topOrigin, 'https://foo.com');
-  assert_equals(ev.paymentRequestOrigin, 'https://bar.com');
-}, 'CanMakePaymentEvent can be constructed with a CanMakePaymentEventInit, even if not trusted');
-
-test(() => {
-  const ev = new CanMakePaymentEvent('test');
-  self.addEventListener('test', evt => {
-    assert_equals(ev, evt);
-  });
-  self.dispatchEvent(ev);
-}, 'CanMakePaymentEvent can be dispatched, even if not trusted');
+done();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.html
new file mode 100644
index 0000000..31ac8caf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for PaymentRequestEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+test(() => {
+  assert_false('PaymentRequestEvent' in window);
+}, 'PaymentRequestEvent constructor must not be exposed in window');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.html b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.html
new file mode 100644
index 0000000..a64c429
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for PaymentRequestEvent Constructor</title>
+<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+service_worker_test('payment-request-event-constructor.https.serviceworker.js',
+    'PaymentRequestEvent can be constructed in service worker');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.js
new file mode 100644
index 0000000..1aea700
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.serviceworker.js
@@ -0,0 +1,45 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+  try {
+    new PaymentRequestEvent('test', undefined);
+    new PaymentRequestEvent('test', null);
+    new PaymentRequestEvent('test', {});
+  } catch (err) {
+    assert_unreached(`Unexpected exception: ${err.message}`);
+  }
+}, 'PaymentRequestEvent can be constucted in service worker.');
+
+test(() => {
+  const ev = new PaymentRequestEvent('test', {
+    bubbles: true,
+    cancelable: true,
+    composed: true,
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_true(ev.bubbles, 'set by EventInitDict');
+  assert_true(ev.cancelable, 'set by EventInitDict');
+  assert_true(ev.composed, 'set by EventInitDict');
+  assert_equals(ev.target, null, 'initially null');
+  assert_equals(ev.type, 'test');
+}, 'PaymentRequestEvent can be constructed with an EventInitDict, even if not trusted');
+
+test(() => {
+  const ev = new PaymentRequestEvent('test', {
+    topOrigin: 'https://foo.com',
+    paymentRequestOrigin: 'https://bar.com',
+    methodData: [],
+    modifiers: [],
+  });
+  assert_false(ev.isTrusted, 'constructed in script, so not be trusted');
+  assert_equals(ev.topOrigin, 'https://foo.com');
+  assert_equals(ev.paymentRequestOrigin, 'https://bar.com');
+}, 'PaymentRequestEvent can be constructed with a PaymentRequestEventInit, even if not trusted');
+
+test(() => {
+  const ev = new PaymentRequestEvent('test', {});
+  self.addEventListener('test', evt => {
+    assert_equals(ev, evt);
+  });
+  self.dispatchEvent(ev);
+}, 'PaymentRequestEvent can be dispatched, even if not trusted');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.worker.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.worker.js
new file mode 100644
index 0000000..fdb71aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/payment-request-event-constructor.https.worker.js
@@ -0,0 +1,7 @@
+importScripts('/resources/testharness.js');
+
+test(() => {
+  assert_false('PaymentRequestEvent' in self);
+}, 'PaymentRequestEvent constructor must not be exposed in worker');
+
+done();
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment-expected.txt
index 556d4d4..2085c39 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment-expected.txt
@@ -48,4 +48,6 @@
 () => f3.element().firstChild === f2.element()
   true
 
+() => UI.html`<div>${[1, 2, 3].map(x => UI.html`<span>${x}</span>`)}</div>`.childNodes.length === 3
+  true
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment.js b/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment.js
index 80b424c..03c19df 100644
--- a/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment.js
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/components/fragment.js
@@ -66,5 +66,7 @@
   check(() => f3.element().firstChild === f2.element());
   TestRunner.addResult('');
 
+  check(() => UI.html`<div>${[1, 2, 3].map(x => UI.html`<span>${x}</span>`)}</div>`.childNodes.length === 3);
+
   TestRunner.completeTest();
 })();
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/xss-DENIED-object-element.html b/third_party/WebKit/LayoutTests/http/tests/security/xss-DENIED-object-element.html
index b8b8f9a..976cc20 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/xss-DENIED-object-element.html
+++ b/third_party/WebKit/LayoutTests/http/tests/security/xss-DENIED-object-element.html
@@ -13,16 +13,12 @@
     document.body.appendChild(object);
     object.onload = function() {
         object.onload = null;
-        object.data = "javascript:window.parent.foo = true; alert(document.body.innerHTML);";
+        object.data = "javascript:alert(document.body.innerHTML)";
         object.innerHTML = "foo";
 
-        if (window.testRunner) {
-            setTimeout(() => {
-                if (window.foo) alert("ERROR");
-                testRunner.notifyDone();
-            }, 50);
-        }
-    };
+        if (window.testRunner)
+            setTimeout("testRunner.notifyDone()", 50);
+    }
 }
 </script>
 </head>
diff --git a/third_party/blink/public/web/web_media_player_action.h b/third_party/blink/public/web/web_media_player_action.h
index 5286624..674b900b 100644
--- a/third_party/blink/public/web/web_media_player_action.h
+++ b/third_party/blink/public/web/web_media_player_action.h
@@ -34,7 +34,15 @@
 namespace blink {
 
 struct WebMediaPlayerAction {
-  enum Type { kUnknown, kPlay, kMute, kLoop, kControls, kTypeLast = kControls };
+  enum Type {
+    kUnknown,
+    kPlay,
+    kMute,
+    kLoop,
+    kControls,
+    kPictureInPicture,
+    kTypeLast = kPictureInPicture
+  };
 
   Type type;
   bool enable;
diff --git a/third_party/blink/renderer/core/css/css_style_rule.idl b/third_party/blink/renderer/core/css/css_style_rule.idl
index 2bfabec..44a3755a 100644
--- a/third_party/blink/renderer/core/css/css_style_rule.idl
+++ b/third_party/blink/renderer/core/css/css_style_rule.idl
@@ -25,5 +25,5 @@
 ] interface CSSStyleRule : CSSRule {
     [SetterCallWith=ExecutionContext] attribute DOMString selectorText;
     [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
-    [SameObject, RuntimeEnabled=CSSTypedOM, MeasureAs=CSSTypedOMStylePropertyMap] readonly attribute StylePropertyMap styleMap;
+    [SameObject, MeasureAs=CSSTypedOMStylePropertyMap] readonly attribute StylePropertyMap styleMap;
 };
diff --git a/third_party/blink/renderer/core/css/cssom/css_image_value.idl b/third_party/blink/renderer/core/css/cssom/css_image_value.idl
index 683bc07..272f96b1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_image_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_image_value.idl
@@ -6,7 +6,6 @@
 // image types.
 // https://drafts.css-houdini.org/css-typed-om/#imagevalue-objects
 [
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     ImplementedAs=CSSStyleImageValue
 ] interface CSSImageValue : CSSStyleValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_keyword_value.idl b/third_party/blink/renderer/core/css/cssom/css_keyword_value.idl
index 22c7060..31f6b517 100644
--- a/third_party/blink/renderer/core/css/cssom/css_keyword_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_keyword_value.idl
@@ -7,7 +7,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#keywordvalue-objects
 [
     Constructor(CSSOMString keyword),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSKeywordValue : CSSStyleValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_invert.idl b/third_party/blink/renderer/core/css/cssom/css_math_invert.idl
index b580f52..c165677b 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_invert.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_invert.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathinvert
 [
   Constructor(CSSNumberish arg),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSMathInvert : CSSMathValue {
   readonly attribute CSSNumberish value;
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_max.idl b/third_party/blink/renderer/core/css/cssom/css_math_max.idl
index 590b3b7e..4a29b526 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_max.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_max.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathsum
 [
   Constructor(CSSNumberish... args),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSMathMax : CSSMathValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_min.idl b/third_party/blink/renderer/core/css/cssom/css_math_min.idl
index a028f4a..fd8fbb1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_min.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_min.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathsum
 [
   Constructor(CSSNumberish... args),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSMathMin : CSSMathValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_negate.idl b/third_party/blink/renderer/core/css/cssom/css_math_negate.idl
index faf4b419..6909553 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_negate.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_negate.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathnegate
 [
   Constructor(CSSNumberish arg),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSMathNegate : CSSMathValue {
   readonly attribute CSSNumberish value;
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_product.idl b/third_party/blink/renderer/core/css/cssom/css_math_product.idl
index 19680ca..f4c4471 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_product.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_product.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathsub
 [
   Constructor(CSSNumberish... args),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSMathProduct : CSSMathValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_sum.idl b/third_party/blink/renderer/core/css/cssom/css_math_sum.idl
index 5b8e037..72b8cb8 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_sum.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_sum.idl
@@ -6,7 +6,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#cssmathsum
 [
   Constructor(CSSNumberish... args),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSMathSum : CSSMathValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_value.idl b/third_party/blink/renderer/core/css/cssom/css_math_value.idl
index 18eb71ee1..f4d6a81 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_math_value.idl
@@ -14,7 +14,6 @@
 };
 
 [
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSMathValue : CSSNumericValue {
   [ImplementedAs=getOperator] readonly attribute CSSMathOperator operator;
diff --git a/third_party/blink/renderer/core/css/cssom/css_matrix_component.idl b/third_party/blink/renderer/core/css/cssom/css_matrix_component.idl
index 5db9a74..530e42f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_matrix_component.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_matrix_component.idl
@@ -8,7 +8,6 @@
 [
     Constructor(DOMMatrixReadOnly matrix,
         optional CSSMatrixComponentOptions options),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSMatrixComponent : CSSTransformComponent {
     attribute DOMMatrix matrix;
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_array.idl b/third_party/blink/renderer/core/css/cssom/css_numeric_array.idl
index c5dc75df..402d98c 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_array.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_array.idl
@@ -5,7 +5,6 @@
 // Represents the sum of one or more CSSNumericValues.
 // https://drafts.css-houdini.org/css-typed-om/#cssmathsum
 [
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSNumericArray {
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_type.idl b/third_party/blink/renderer/core/css/cssom/css_numeric_type.idl
index 5584af4..e539ea80 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_type.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_type.idl
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 [
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] enum CSSNumericBaseType {
     "length",
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_value.idl b/third_party/blink/renderer/core/css/cssom/css_numeric_value.idl
index eb633b6d1..ae86a128 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_value.idl
@@ -7,7 +7,6 @@
 typedef (double or CSSNumericValue) CSSNumberish;
 
 [
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSNumericValue : CSSStyleValue {
   [RaisesException, NewObject] CSSNumericValue add(CSSNumberish... values);
diff --git a/third_party/blink/renderer/core/css/cssom/css_perspective.idl b/third_party/blink/renderer/core/css/cssom/css_perspective.idl
index 2fbea49..9cba1d1f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_perspective.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_perspective.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssperspective
 [
     Constructor(CSSNumericValue length),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSPerspective : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_position_value.idl b/third_party/blink/renderer/core/css/cssom/css_position_value.idl
index 0edf326..3954abb 100644
--- a/third_party/blink/renderer/core/css/cssom/css_position_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_position_value.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#positionvalue-objects
 [
   Constructor(CSSNumericValue x, CSSNumericValue y),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet),
   RaisesException=Constructor
 ] interface CSSPositionValue : CSSStyleValue {
diff --git a/third_party/blink/renderer/core/css/cssom/css_rotate.idl b/third_party/blink/renderer/core/css/cssom/css_rotate.idl
index f2fe3a9..1b19a86 100644
--- a/third_party/blink/renderer/core/css/cssom/css_rotate.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_rotate.idl
@@ -8,7 +8,6 @@
 [
     Constructor(CSSNumericValue angleValue),
     Constructor(CSSNumberish x, CSSNumberish y, CSSNumberish z, CSSNumericValue angle),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSRotate : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_scale.idl b/third_party/blink/renderer/core/css/cssom/css_scale.idl
index cd5d3c2..2644e70b 100644
--- a/third_party/blink/renderer/core/css/cssom/css_scale.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_scale.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssscale
 [
     Constructor(CSSNumberish x, CSSNumberish y, optional CSSNumberish z),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSScale : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew.idl b/third_party/blink/renderer/core/css/cssom/css_skew.idl
index d600a54..8aed33f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_skew.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssskew
 [
     Constructor(CSSNumericValue ax, CSSNumericValue ay),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSSkew : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew_x.idl b/third_party/blink/renderer/core/css/cssom/css_skew_x.idl
index 56c29e62..fc08ea50 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew_x.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_skew_x.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssskewx
 [
     Constructor(CSSNumericValue ax),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
     RaisesException=Constructor]
 interface CSSSkewX : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew_y.idl b/third_party/blink/renderer/core/css/cssom/css_skew_y.idl
index 692e00c4..eea0517 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew_y.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_skew_y.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssskewy
 [
     Constructor(CSSNumericValue ay),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
     RaisesException=Constructor]
 interface CSSSkewY : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_style_value.idl b/third_party/blink/renderer/core/css/cssom/css_style_value.idl
index 88ae318d..d63a4b1a 100644
--- a/third_party/blink/renderer/core/css/cssom/css_style_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_style_value.idl
@@ -7,7 +7,7 @@
 // base CSSStyleValues.
 // Spec: https://drafts.css-houdini.org/css-typed-om/#stylevalue-objects
 [
-  Exposed(Window CSSTypedOM,LayoutWorklet CSSTypedOM,PaintWorklet CSSPaintAPI)
+  Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSStyleValue {
   stringifier;
   // Putting Exposed=Window in the next line makes |parse| not exposed to Worklets.
diff --git a/third_party/blink/renderer/core/css/cssom/css_transform_component.idl b/third_party/blink/renderer/core/css/cssom/css_transform_component.idl
index ab1d67b..cfb17835 100644
--- a/third_party/blink/renderer/core/css/cssom/css_transform_component.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_transform_component.idl
@@ -7,7 +7,6 @@
 // before they can be used as a value for properties like "transform".
 // Spec: https://drafts.css-houdini.org/css-typed-om/#csstransformcomponent
 [
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSTransformComponent {
     stringifier;
diff --git a/third_party/blink/renderer/core/css/cssom/css_transform_value.idl b/third_party/blink/renderer/core/css/cssom/css_transform_value.idl
index d6b71ee..7c654d1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_transform_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_transform_value.idl
@@ -5,7 +5,6 @@
 [
     Constructor(sequence<CSSTransformComponent> transforms),
     RaisesException=Constructor,
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSTransformValue : CSSStyleValue {
     iterable<CSSTransformComponent>;
diff --git a/third_party/blink/renderer/core/css/cssom/css_translate.idl b/third_party/blink/renderer/core/css/cssom/css_translate.idl
index ca272a67..51f2ce9 100644
--- a/third_party/blink/renderer/core/css/cssom/css_translate.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_translate.idl
@@ -8,7 +8,6 @@
 [
     Constructor(CSSNumericValue x, CSSNumericValue y,
         optional CSSNumericValue z),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor
 ] interface CSSTranslate : CSSTransformComponent {
diff --git a/third_party/blink/renderer/core/css/cssom/css_unit_value.idl b/third_party/blink/renderer/core/css/cssom/css_unit_value.idl
index fe651c9..b0be5d7b 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unit_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_unit_value.idl
@@ -8,7 +8,6 @@
 [
   Constructor(double value, CSSOMString unit),
   RaisesException=Constructor,
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSUnitValue : CSSNumericValue {
   attribute double value;
diff --git a/third_party/blink/renderer/core/css/cssom/css_unit_values.idl b/third_party/blink/renderer/core/css/cssom/css_unit_values.idl
index 5f1eeb3a..36a25b5 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unit_values.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_unit_values.idl
@@ -5,8 +5,7 @@
 // https://drafts.css-houdini.org/css-typed-om/#numeric-factory
 
 [
-  ImplementedAs=CSSUnitValues,
-  RuntimeEnabled=CSSTypedOM
+  ImplementedAs=CSSUnitValues
 ] partial interface CSS {
   [NewObject] static CSSUnitValue number(double value);
   [NewObject] static CSSUnitValue percent(double value);
diff --git a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.idl b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.idl
index 236fcd1..685c20f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.idl
@@ -7,7 +7,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#unparsedvalue-objects
 [
   Constructor(sequence<CSSUnparsedSegment> members),
-  RuntimeEnabled=CSSTypedOM,
   Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface CSSUnparsedValue : CSSStyleValue {
     iterable<CSSUnparsedSegment>;
diff --git a/third_party/blink/renderer/core/css/cssom/css_variable_reference_value.idl b/third_party/blink/renderer/core/css/cssom/css_variable_reference_value.idl
index 8da21e8..ca46773 100644
--- a/third_party/blink/renderer/core/css/cssom/css_variable_reference_value.idl
+++ b/third_party/blink/renderer/core/css/cssom/css_variable_reference_value.idl
@@ -6,7 +6,6 @@
 // Spec: https://drafts.css-houdini.org/css-typed-om/#cssvariablereferencevalue
 [
     Constructor(CSSOMString variable, optional CSSUnparsedValue? fallback = null),
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window,LayoutWorklet,PaintWorklet),
     RaisesException=Constructor,
     ImplementedAs=CSSStyleVariableReferenceValue
diff --git a/third_party/blink/renderer/core/css/cssom/element_computed_style_map.idl b/third_party/blink/renderer/core/css/cssom/element_computed_style_map.idl
index b212058e..38f0117 100644
--- a/third_party/blink/renderer/core/css/cssom/element_computed_style_map.idl
+++ b/third_party/blink/renderer/core/css/cssom/element_computed_style_map.idl
@@ -5,8 +5,7 @@
 // https://drafts.css-houdini.org/css-typed-om/#computed-stylepropertymapreadonly-objects
 
 [
-    ImplementedAs=ElementComputedStyleMap,
-    RuntimeEnabled=CSSTypedOM
+    ImplementedAs=ElementComputedStyleMap
 ] partial interface Element {
     [MeasureAs=CSSTypedOMStylePropertyMap] StylePropertyMapReadOnly computedStyleMap();
 };
diff --git a/third_party/blink/renderer/core/css/cssom/style_property_map.idl b/third_party/blink/renderer/core/css/cssom/style_property_map.idl
index c8d3b1c..775ab0b 100644
--- a/third_party/blink/renderer/core/css/cssom/style_property_map.idl
+++ b/third_party/blink/renderer/core/css/cssom/style_property_map.idl
@@ -5,7 +5,6 @@
 // https://drafts.css-houdini.org/css-typed-om/#the-stylepropertymap
 
 [
-    RuntimeEnabled=CSSTypedOM,
     Exposed=(Window)
 ] interface StylePropertyMap : StylePropertyMapReadOnly {
     // TODO(https://crbug.com/838890): DOMString should be CSSOMString
diff --git a/third_party/blink/renderer/core/css/cssom/style_property_map_read_only.idl b/third_party/blink/renderer/core/css/cssom/style_property_map_read_only.idl
index 8a73e33c..5fab519 100644
--- a/third_party/blink/renderer/core/css/cssom/style_property_map_read_only.idl
+++ b/third_party/blink/renderer/core/css/cssom/style_property_map_read_only.idl
@@ -4,7 +4,7 @@
 
 // Spec: https://drafts.css-houdini.org/css-typed-om/#the-stylepropertymap
 [
-    Exposed(Window CSSTypedOM,LayoutWorklet CSSTypedOM,PaintWorklet CSSPaintAPI)
+    Exposed=(Window,LayoutWorklet,PaintWorklet)
 ] interface StylePropertyMapReadOnly {
     iterable<CSSOMString, sequence<CSSStyleValue>>;
     /* TODO: This should return (undefined or CSSStyleValue),
diff --git a/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc b/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
index 9eb7099..cda4b48 100644
--- a/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
+++ b/third_party/blink/renderer/core/css/parser/css_property_parser_helpers.cc
@@ -1384,8 +1384,6 @@
 
 static CSSValue* ConsumePaint(CSSParserTokenRange& args,
                               const CSSParserContext* context) {
-  DCHECK(RuntimeEnabledFeatures::CSSPaintAPIEnabled());
-
   const CSSParserToken& name_token = args.ConsumeIncludingWhitespace();
   CSSCustomIdentValue* name = ConsumeCustomIdentWithToken(name_token);
   if (!name)
@@ -1465,10 +1463,7 @@
   } else if (id == CSSValueWebkitCrossFade) {
     result = ConsumeCrossFade(args, context);
   } else if (id == CSSValuePaint) {
-    result = context->IsSecureContext() &&
-                     RuntimeEnabledFeatures::CSSPaintAPIEnabled()
-                 ? ConsumePaint(args, context)
-                 : nullptr;
+    result = context->IsSecureContext() ? ConsumePaint(args, context) : nullptr;
   }
   if (!result || !args.AtEnd())
     return nullptr;
diff --git a/third_party/blink/renderer/core/dom/element.idl b/third_party/blink/renderer/core/dom/element.idl
index 0fbc8aa3..57791132 100644
--- a/third_party/blink/renderer/core/dom/element.idl
+++ b/third_party/blink/renderer/core/dom/element.idl
@@ -120,7 +120,7 @@
 
     // Typed OM
     // https://drafts.css-houdini.org/css-typed-om/#inline-stylepropertymap-objects
-    [RuntimeEnabled=CSSTypedOM, SameObject, MeasureAs=CSSTypedOMStylePropertyMap] readonly attribute StylePropertyMap attributeStyleMap;
+    [SameObject, MeasureAs=CSSTypedOMStylePropertyMap] readonly attribute StylePropertyMap attributeStyleMap;
 
     // Non-standard API
     [MeasureAs=ElementScrollIntoViewIfNeeded] void scrollIntoViewIfNeeded(optional boolean centerIfNeeded);
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index d3d1a9b..66c64e4 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -13128,125 +13128,4 @@
             frame->GetDocument().CanonicalUrlForSharing());
 }
 
-class WebFramePluginElementTest : public WebFrameTest {
- public:
-  WebFramePluginElementTest() {
-    webview_helper_.InitializeAndLoad("about:blank", &main_frame_client_);
-  }
-  ~WebFramePluginElementTest() override { webview_helper_.Reset(); }
-
- protected:
-  // Tester for verifying <embed>/<object> with a content frame is properly
-  // cleaned up when 'src'/'data' attributes are changed.
-  void RunTestLogicForPluginType(const char* plugin_tag,
-                                 const char* url_attribute_name) {
-    constexpr char kPageWithBeforeUnloadHTMLString[] = R"(
-      data: text/html,
-      <body>
-        <script>
-          window.addEventListener('beforeunload', (e) => {
-          e.returnValue = 'message';
-          return e.returnValue;
-          });
-        </script>
-      <body>;)";
-    constexpr char kSimplePageHTMLString[] = "data: text/html,<body></body>";
-    AppendElement(plugin_tag);
-    SetAttribute(plugin_tag, "type", "text/html");
-    SetAttribute(plugin_tag, url_attribute_name,
-                 kPageWithBeforeUnloadHTMLString);
-    EXPECT_FALSE(GetMainFrame()->FirstChild());
-    // After the attribute update the plugin needs update. This will ensure that
-    // happens and HTMLPlugInElement::RequestObject is invoked. After this step
-    // the plugin must have a content frame.
-    GetMainFrame()->View()->UpdateAllLifecyclePhases();
-    RunPendingTasks();
-    auto* child_frame = GetMainFrame()->FirstChild();
-    EXPECT_TRUE(child_frame);
-    SetAttribute(plugin_tag, url_attribute_name, kSimplePageHTMLString);
-    GetMainFrame()->View()->UpdateAllLifecyclePhases();
-    RunPendingTasks();
-    EXPECT_EQ(1U, main_frame_client_.removed_child_frame_count());
-    EXPECT_EQ(2U, main_frame_client_.created_child_frames_count());
-    EXPECT_NE(child_frame, GetMainFrame()->FirstChild());
-  }
-
- private:
-  // Used for main frame to keep track of the children which get removed.
-  class MainFrameClient : public FrameTestHelpers::TestWebFrameClient {
-   public:
-    WebLocalFrame* CreateChildFrame(
-        WebLocalFrame* parent,
-        WebTreeScopeType scope,
-        const WebString& name,
-        const WebString& fallback_name,
-        WebSandboxFlags sandbox_flags,
-        const ParsedFeaturePolicy& container_policy,
-        const WebFrameOwnerProperties& frame_owner_properties) override {
-      created_child_frames_count_++;
-      auto child_frame_client =
-          std::make_unique<ChildFrameClient>(&removed_child_frame_count_);
-      return FrameTestHelpers::CreateLocalChild(*parent, scope,
-                                                std::move(child_frame_client));
-    }
-
-    size_t removed_child_frame_count() const {
-      return removed_child_frame_count_;
-    }
-    size_t created_child_frames_count() const {
-      return created_child_frames_count_;
-    }
-
-   private:
-    size_t removed_child_frame_count_ = 0;
-    size_t created_child_frames_count_ = 0;
-  };
-
-  // Notifies the top frame's client about the removal of its frame.
-  class ChildFrameClient : public FrameTestHelpers::TestWebFrameClient {
-   public:
-    ChildFrameClient(size_t* removed_frame_count)
-        : removed_frame_count_(removed_frame_count) {}
-    void FrameDetached(DetachType type) override {
-      if (type == DetachType::kRemove)
-        ++(*removed_frame_count_);
-      FrameTestHelpers::TestWebFrameClient::FrameDetached(type);
-    }
-
-   private:
-    size_t* const removed_frame_count_;
-  };
-
-  WebLocalFrameImpl* GetMainFrame() const {
-    return webview_helper_.LocalMainFrame();
-  }
-
-  void AppendElement(const char* element_type) {
-    String script = String::Format(
-        "document.body.appendChild(document.createElement('%s'));",
-        element_type);
-    GetMainFrame()->ExecuteScript(WebScriptSource(script));
-  }
-
-  void SetAttribute(const char* sel, const char* name, const char* value) {
-    String script =
-        String::Format("document.querySelector('%s').setAttribute('%s', '%s');",
-                       sel, name, value);
-    GetMainFrame()->ExecuteScript(WebScriptSource(script));
-  }
-
-  FrameTestHelpers::WebViewHelper webview_helper_;
-  MainFrameClient main_frame_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(WebFramePluginElementTest);
-};
-
-TEST_F(WebFramePluginElementTest, ChangingEmbedSourceDetachesContentFrame) {
-  RunTestLogicForPluginType("embed", "src");
-}
-
-TEST_F(WebFramePluginElementTest, ChangingObjectDataDetachesContentFrame) {
-  RunTestLogicForPluginType("object", "data");
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 14044fb..177e0594 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -100,6 +100,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
+#include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
 #include "third_party/blink/renderer/core/frame/remote_frame.h"
 #include "third_party/blink/renderer/core/frame/resize_viewport_anchor.h"
 #include "third_party/blink/renderer/core/frame/rotation_viewport_anchor.h"
@@ -112,6 +113,7 @@
 #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
 #include "third_party/blink/renderer/core/html/html_plugin_element.h"
 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
+#include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/core/html/plugin_document.h"
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
@@ -3045,6 +3047,12 @@
       media_element->SetBooleanAttribute(HTMLNames::controlsAttr,
                                          action.enable);
       break;
+    case WebMediaPlayerAction::kPictureInPicture:
+      DCHECK(media_element->IsHTMLVideoElement());
+      // TODO(crbug.com/840516): Toggle PiP instead.
+      PictureInPictureController::From(node->GetDocument())
+          .EnterPictureInPicture(ToHTMLVideoElement(media_element), nullptr);
+      break;
     default:
       NOTREACHED();
   }
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.cc b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
index 5325cfd..c8e4116 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.cc
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
@@ -420,11 +420,6 @@
 
   owned_widget_client_.reset();
   frame_->Close();
-  // TODO(ekaramad): In test FallbackForNonexistentProvisionalNavigation the
-  // child frame inside <object> get destroyed due to attribute change but the
-  // same client is reused (not self owned). We should clean |frame_| so that
-  // the DCHECK in Bind() would not fire on a non-nullptr |frame_|.
-  frame_ = nullptr;
   self_owned_.reset();
 }
 
diff --git a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
index 0112287e..ced9461 100644
--- a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
+++ b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
@@ -40,6 +40,10 @@
     kDisabledByAttribute,
   };
 
+  // Enter Picture-in-Picture for a video element and resolve promise.
+  virtual void EnterPictureInPicture(HTMLVideoElement*,
+                                     ScriptPromiseResolver*) = 0;
+
   // Returns whether a given video element in a document associated with the
   // controller is allowed to request Picture-in-Picture.
   virtual Status IsElementAllowed(const HTMLVideoElement&) const = 0;
diff --git a/third_party/blink/renderer/core/html/html_plugin_element.cc b/third_party/blink/renderer/core/html/html_plugin_element.cc
index 29ca435..d849d69 100644
--- a/third_party/blink/renderer/core/html/html_plugin_element.cc
+++ b/third_party/blink/renderer/core/html/html_plugin_element.cc
@@ -26,7 +26,6 @@
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/css_property_names.h"
-#include "third_party/blink/renderer/core/dom/child_frame_disconnector.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/dom/node.h"
@@ -149,11 +148,12 @@
 
 bool HTMLPlugInElement::RequestObjectInternal(
     const PluginParameters& plugin_params) {
-  DCHECK(!ContentFrame());
-  DCHECK(!ProtocolIsJavaScript(url_));
   if (url_.IsEmpty() && service_type_.IsEmpty())
     return false;
 
+  if (ProtocolIsJavaScript(url_))
+    return false;
+
   KURL completed_url =
       url_.IsEmpty() ? KURL() : GetDocument().CompleteURL(url_);
   if (!AllowedToLoadObject(completed_url, service_type_))
@@ -300,20 +300,6 @@
   WebPluginContainerImpl* plugin = OwnedPlugin();
   if (plugin && context.performing_reattach) {
     SetPersistedPlugin(ToWebPluginContainerImpl(ReleaseEmbeddedContentView()));
-  } else if (ContentFrame() && !UseFallbackContent()) {
-    // In case the element is going to show fallback content, leave the content
-    // frame attached (but kill the FrameView below). Note: this is almost
-    // certainly buggy.
-
-    // TODO(ekaramad, dcheng): There is an inconsistency here between Firefox
-    // and Chrome. In Chrome, <object data="abc"></object> will attempt to load
-    // abc as a frame. The URL won't resolve, so load failure is reported. This
-    // should cause the fallback content to be rendered--in the case of
-    // <object>, the fallback content should be the children of <object>,
-    // ignoring leading <param> children. Very likely, leaving the content frame
-    // attached here is a bug (https://crbug.com/846442)
-    ChildFrameDisconnector(*this).Disconnect(
-        ChildFrameDisconnector::kRootAndDescendants);
   } else {
     // Clear the plugin; will trigger disposal of it with Oilpan.
     SetEmbeddedContentView(nullptr);
@@ -549,7 +535,10 @@
 // We don't use m_url, as it may not be the final URL that the object loads,
 // depending on <param> values.
 bool HTMLPlugInElement::AllowedToLoadFrameURL(const String& url) {
-  return !GetDocument().CompleteURL(url).ProtocolIsJavaScript();
+  KURL complete_url = GetDocument().CompleteURL(url);
+  return !(ContentFrame() && complete_url.ProtocolIsJavaScript() &&
+           !GetDocument().GetSecurityOrigin()->CanAccess(
+               ContentFrame()->GetSecurityContext()->GetSecurityOrigin()));
 }
 
 bool HTMLPlugInElement::RequestObject(const PluginParameters& plugin_params) {
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index 9fe495e..d6c057e 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -1164,8 +1164,7 @@
     auto children =
         NGInlineFragmentTraversal::SelfFragmentsOf(*box_fragment, this);
     for (const auto& child : children) {
-      NGPhysicalOffsetRect child_rect =
-          child.fragment->VisualRectWithContents();
+      NGPhysicalOffsetRect child_rect = child.fragment->InkOverflow();
       child_rect.offset += child.offset_to_container_box;
       result.Unite(child_rect);
     }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
index 654b7eb..26a4c9d7 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm_test.cc
@@ -433,8 +433,8 @@
   EXPECT_EQ(LayoutUnit(53), span->OffsetLeft());
 }
 
-// Test glyph bounding box causes visual overflow.
-TEST_F(NGInlineLayoutAlgorithmTest, VisualRect) {
+// Test glyph bounding box causes ink overflow.
+TEST_F(NGInlineLayoutAlgorithmTest, InkOverflow) {
   LoadAhem();
   SetBodyInnerHTML(R"HTML(
     <!DOCTYPE html>
@@ -452,9 +452,9 @@
 
   EXPECT_EQ(LayoutUnit(10), box_fragment->Size().height);
 
-  NGPhysicalOffsetRect visual_rect = box_fragment->ContentsVisualRect();
-  EXPECT_EQ(LayoutUnit(-5), visual_rect.offset.top);
-  EXPECT_EQ(LayoutUnit(20), visual_rect.size.height);
+  NGPhysicalOffsetRect ink_overflow = box_fragment->ContentsInkOverflow();
+  EXPECT_EQ(LayoutUnit(-5), ink_overflow.offset.top);
+  EXPECT_EQ(LayoutUnit(20), ink_overflow.size.height);
 }
 
 TEST_F(NGInlineLayoutAlgorithmTest,
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
index bedef045..900936c 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
@@ -132,13 +132,13 @@
   WritingMode writing_mode(node_.Style().GetWritingMode());
   NGPhysicalSize physical_size = Size().ConvertToPhysical(writing_mode);
 
-  NGPhysicalOffsetRect contents_visual_rect({}, physical_size);
+  NGPhysicalOffsetRect contents_ink_overflow({}, physical_size);
   NGPhysicalOffsetRect scrollable_overflow({}, physical_size);
   for (size_t i = 0; i < children_.size(); ++i) {
     NGPhysicalFragment* child = children_[i].get();
     child->SetOffset(offsets_[i].ConvertToPhysical(
         writing_mode, Direction(), physical_size, child->Size()));
-    child->PropagateContentsVisualRect(&contents_visual_rect);
+    child->PropagateContentsInkOverflow(&contents_ink_overflow);
     NGPhysicalOffsetRect child_scroll_overflow = child->ScrollableOverflow();
     child_scroll_overflow.offset += child->Offset();
     scrollable_overflow.Unite(child_scroll_overflow);
@@ -147,7 +147,7 @@
   scoped_refptr<NGPhysicalLineBoxFragment> fragment =
       base::AdoptRef(new NGPhysicalLineBoxFragment(
           Style(), style_variant_, physical_size, children_,
-          contents_visual_rect, scrollable_overflow, metrics_, base_direction_,
+          contents_ink_overflow, scrollable_overflow, metrics_, base_direction_,
           break_token_ ? std::move(break_token_)
                        : NGInlineBreakToken::Create(node_)));
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
index e341ddff..fbf9535 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.cc
@@ -45,7 +45,7 @@
     NGStyleVariant style_variant,
     NGPhysicalSize size,
     Vector<scoped_refptr<NGPhysicalFragment>>& children,
-    const NGPhysicalOffsetRect& contents_visual_rect,
+    const NGPhysicalOffsetRect& contents_ink_overflow,
     const NGPhysicalOffsetRect& scrollable_overflow,
     const NGLineHeightMetrics& metrics,
     TextDirection base_direction,
@@ -57,7 +57,7 @@
                                   kFragmentLineBox,
                                   0,
                                   children,
-                                  contents_visual_rect,
+                                  contents_ink_overflow,
                                   std::move(break_token)),
       scrollable_overflow_(scrollable_overflow),
       metrics_(metrics) {
@@ -71,8 +71,8 @@
   return metrics_.ascent;
 }
 
-NGPhysicalOffsetRect NGPhysicalLineBoxFragment::VisualRectWithContents() const {
-  return ContentsVisualRect();
+NGPhysicalOffsetRect NGPhysicalLineBoxFragment::InkOverflow() const {
+  return ContentsInkOverflow();
 }
 
 const NGPhysicalFragment* NGPhysicalLineBoxFragment::FirstLogicalLeaf() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
index b92b910..296475d9 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h
@@ -20,7 +20,7 @@
                             NGStyleVariant style_variant,
                             NGPhysicalSize size,
                             Vector<scoped_refptr<NGPhysicalFragment>>& children,
-                            const NGPhysicalOffsetRect& contents_visual_rect,
+                            const NGPhysicalOffsetRect& contents_ink_overflow,
                             const NGPhysicalOffsetRect& scrollable_overflow,
                             const NGLineHeightMetrics&,
                             TextDirection base_direction,
@@ -38,8 +38,8 @@
   // Compute baseline for the specified baseline type.
   LayoutUnit BaselinePosition(FontBaseline) const;
 
-  // VisualRect of itself including contents, in the local coordinate.
-  NGPhysicalOffsetRect VisualRectWithContents() const;
+  // Ink overflow of itself including contents, in the local coordinate.
+  NGPhysicalOffsetRect InkOverflow() const;
 
   // Scrollable overflow. including contents, in the local coordinate.
   NGPhysicalOffsetRect ScrollableOverflow() const {
@@ -60,7 +60,7 @@
   scoped_refptr<NGPhysicalFragment> CloneWithoutOffset() const {
     Vector<scoped_refptr<NGPhysicalFragment>> children_copy(children_);
     return base::AdoptRef(new NGPhysicalLineBoxFragment(
-        Style(), StyleVariant(), size_, children_copy, contents_visual_rect_,
+        Style(), StyleVariant(), size_, children_copy, contents_ink_overflow_,
         scrollable_overflow_, metrics_, BaseDirection(), break_token_));
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
index d75f591..72140e16 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.cc
@@ -89,23 +89,24 @@
   return {};
 }
 
-NGPhysicalOffsetRect NGPhysicalTextFragment::SelfVisualRect() const {
+NGPhysicalOffsetRect NGPhysicalTextFragment::SelfInkOverflow() const {
   if (UNLIKELY(!shape_result_))
     return LocalRect();
 
   // Glyph bounds is in logical coordinate, origin at the alphabetic baseline.
-  LayoutRect visual_rect = EnclosingLayoutRect(shape_result_->Bounds());
+  LayoutRect ink_overflow = EnclosingLayoutRect(shape_result_->Bounds());
 
   // Make the origin at the logical top of this fragment.
   const ComputedStyle& style = Style();
   const Font& font = style.GetFont();
   if (const SimpleFontData* font_data = font.PrimaryFont()) {
-    visual_rect.SetY(visual_rect.Y() + font_data->GetFontMetrics().FixedAscent(
-                                           kAlphabeticBaseline));
+    ink_overflow.SetY(
+        ink_overflow.Y() +
+        font_data->GetFontMetrics().FixedAscent(kAlphabeticBaseline));
   }
 
   if (float stroke_width = style.TextStrokeWidth()) {
-    visual_rect.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
+    ink_overflow.Inflate(LayoutUnit::FromFloatCeil(stroke_width / 2.0f));
   }
 
   if (style.GetTextEmphasisMark() != TextEmphasisMark::kNone) {
@@ -113,13 +114,13 @@
         LayoutUnit(font.EmphasisMarkHeight(style.TextEmphasisMarkString()));
     DCHECK_GT(emphasis_mark_height, LayoutUnit());
     if (style.GetTextEmphasisLineLogicalSide() == LineLogicalSide::kOver) {
-      visual_rect.ShiftYEdgeTo(
-          std::min(visual_rect.Y(), -emphasis_mark_height));
+      ink_overflow.ShiftYEdgeTo(
+          std::min(ink_overflow.Y(), -emphasis_mark_height));
     } else {
       LayoutUnit logical_height =
           style.IsHorizontalWritingMode() ? Size().height : Size().width;
-      visual_rect.ShiftMaxYEdgeTo(
-          std::max(visual_rect.MaxY(), logical_height + emphasis_mark_height));
+      ink_overflow.ShiftMaxYEdgeTo(
+          std::max(ink_overflow.MaxY(), logical_height + emphasis_mark_height));
     }
   }
 
@@ -129,16 +130,16 @@
             LayoutRectOutsets(text_shadow->RectOutsetsIncludingOriginal()),
             style.GetWritingMode());
     text_shadow_logical_outsets.ClampNegativeToZero();
-    visual_rect.Expand(text_shadow_logical_outsets);
+    ink_overflow.Expand(text_shadow_logical_outsets);
   }
 
-  visual_rect = LayoutRect(EnclosingIntRect(visual_rect));
+  ink_overflow = LayoutRect(EnclosingIntRect(ink_overflow));
 
   // Uniting the frame rect ensures that non-ink spaces such side bearings, or
   // even space characters, are included in the visual rect for decorations.
-  NGPhysicalOffsetRect local_visual_rect = ConvertToLocal(visual_rect);
-  local_visual_rect.Unite(LocalRect());
-  return local_visual_rect;
+  NGPhysicalOffsetRect local_ink_overflow = ConvertToLocal(ink_overflow);
+  local_ink_overflow.Unite(LocalRect());
+  return local_ink_overflow;
 }
 
 scoped_refptr<NGPhysicalFragment> NGPhysicalTextFragment::TrimText(
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
index 5819a21a..729b25d 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h
@@ -115,7 +115,7 @@
 
   // The visual bounding box that includes glpyh bounding box and CSS
   // properties, in local coordinates.
-  NGPhysicalOffsetRect SelfVisualRect() const;
+  NGPhysicalOffsetRect SelfInkOverflow() const;
 
   NGTextEndEffect EndEffect() const {
     return static_cast<NGTextEndEffect>(end_effect_);
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
index aa08a9e4..4d605e51 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
@@ -76,13 +76,13 @@
     if (const NGPhysicalBoxFragment* physical_fragment = CurrentFragment()) {
       AddScrollingOverflowFromChildren();
       Base::AddSelfVisualOverflow(
-          physical_fragment->SelfVisualRect().ToLayoutFlippedRect(
+          physical_fragment->SelfInkOverflow().ToLayoutFlippedRect(
               physical_fragment->Style(), physical_fragment->Size()));
       // TODO(kojii): If |RecalcOverflowAfterStyleChange()|, we need to
       // re-compute glyph bounding box. How to detect it and how to re-compute
       // is TBD.
       Base::AddContentsVisualOverflow(
-          physical_fragment->ContentsVisualRect().ToLayoutFlippedRect(
+          physical_fragment->ContentsInkOverflow().ToLayoutFlippedRect(
               physical_fragment->Style(), physical_fragment->Size()));
       // TODO(kojii): The above code computes visual overflow only, we fallback
       // to LayoutBlock for AddLayoutOverflow() for now. It doesn't compute
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
index 028a6d22..3314244 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
@@ -257,12 +257,12 @@
 
   NGPhysicalSize physical_size = Size().ConvertToPhysical(GetWritingMode());
 
-  NGPhysicalOffsetRect contents_visual_rect({}, physical_size);
+  NGPhysicalOffsetRect contents_ink_overflow({}, physical_size);
   for (size_t i = 0; i < children_.size(); ++i) {
     NGPhysicalFragment* child = children_[i].get();
     child->SetOffset(offsets_[i].ConvertToPhysical(
         GetWritingMode(), Direction(), physical_size, child->Size()));
-    child->PropagateContentsVisualRect(&contents_visual_rect);
+    child->PropagateContentsInkOverflow(&contents_ink_overflow);
   }
 
   scoped_refptr<NGBreakToken> break_token;
@@ -287,7 +287,7 @@
           layout_object_, Style(), style_variant_, physical_size, children_,
           padding_.ConvertToPhysical(GetWritingMode(), Direction())
               .SnapToDevicePixels(),
-          contents_visual_rect, baselines_, BoxType(), is_old_layout_root_,
+          contents_ink_overflow, baselines_, BoxType(), is_old_layout_root_,
           border_edges_.ToPhysical(GetWritingMode()), std::move(break_token)));
 
   Vector<NGPositionedFloat> positioned_floats;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index de64228b..92d0a72 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -21,7 +21,7 @@
     NGPhysicalSize size,
     Vector<scoped_refptr<NGPhysicalFragment>>& children,
     const NGPixelSnappedPhysicalBoxStrut& padding,
-    const NGPhysicalOffsetRect& contents_visual_rect,
+    const NGPhysicalOffsetRect& contents_ink_overflow,
     Vector<NGBaseline>& baselines,
     NGBoxType box_type,
     bool is_old_layout_root,
@@ -34,7 +34,7 @@
                                   kFragmentBox,
                                   box_type,
                                   children,
-                                  contents_visual_rect,
+                                  contents_ink_overflow,
                                   std::move(break_token)),
       baselines_(std::move(baselines)),
       padding_(padding) {
@@ -138,30 +138,30 @@
   return LayoutSize(box->ScrollWidth(), box->ScrollHeight());
 }
 
-NGPhysicalOffsetRect NGPhysicalBoxFragment::SelfVisualRect() const {
+NGPhysicalOffsetRect NGPhysicalBoxFragment::SelfInkOverflow() const {
   const ComputedStyle& style = Style();
-  LayoutRect visual_rect({}, Size().ToLayoutSize());
+  LayoutRect ink_overflow({}, Size().ToLayoutSize());
 
   DCHECK(GetLayoutObject());
   if (style.HasVisualOverflowingEffect()) {
     if (GetLayoutObject()->IsBox()) {
-      visual_rect.Expand(style.BoxDecorationOutsets());
+      ink_overflow.Expand(style.BoxDecorationOutsets());
       if (style.HasOutline()) {
         Vector<LayoutRect> outline_rects;
         // The result rects are in coordinates of this object's border box.
         AddSelfOutlineRects(&outline_rects, LayoutPoint());
         LayoutRect rect = UnionRectEvenIfEmpty(outline_rects);
         rect.Inflate(style.OutlineOutsetExtent());
-        visual_rect.Unite(rect);
+        ink_overflow.Unite(rect);
       }
     } else {
       // TODO(kojii): Implement for inline boxes.
       DCHECK(GetLayoutObject()->IsLayoutInline());
-      visual_rect.Expand(style.BoxDecorationOutsets());
+      ink_overflow.Expand(style.BoxDecorationOutsets());
     }
   }
-  visual_rect.Unite(descendant_outlines_.ToLayoutRect());
-  return NGPhysicalOffsetRect(visual_rect);
+  ink_overflow.Unite(descendant_outlines_.ToLayoutRect());
+  return NGPhysicalOffsetRect(ink_overflow);
 }
 
 void NGPhysicalBoxFragment::AddSelfOutlineRects(
@@ -205,7 +205,7 @@
       DCHECK(child->GetLayoutObject());
       LayoutObject* child_layout = child->GetLayoutObject();
       Vector<LayoutRect> child_rects;
-      child_rects.push_back(child->VisualRectWithContents().ToLayoutRect());
+      child_rects.push_back(child->InkOverflow().ToLayoutRect());
       child_layout->LocalToAncestorRects(
           child_rects, ToLayoutBoxModelObject(GetLayoutObject()), LayoutPoint(),
           additional_offset);
@@ -215,14 +215,13 @@
   }
 }
 
-NGPhysicalOffsetRect NGPhysicalBoxFragment::VisualRectWithContents(
-    bool clip_overflow) const {
-  if ((clip_overflow && HasOverflowClip()) || Style().HasMask())
-    return SelfVisualRect();
+NGPhysicalOffsetRect NGPhysicalBoxFragment::InkOverflow(bool apply_clip) const {
+  if ((apply_clip && HasOverflowClip()) || Style().HasMask())
+    return SelfInkOverflow();
 
-  NGPhysicalOffsetRect visual_rect = SelfVisualRect();
-  visual_rect.Unite(ContentsVisualRect());
-  return visual_rect;
+  NGPhysicalOffsetRect ink_overflow = SelfInkOverflow();
+  ink_overflow.Unite(ContentsInkOverflow());
+  return ink_overflow;
 }
 
 UBiDiLevel NGPhysicalBoxFragment::BidiLevel() const {
@@ -247,7 +246,7 @@
   scoped_refptr<NGPhysicalFragment> physical_fragment =
       base::AdoptRef(new NGPhysicalBoxFragment(
           layout_object_, Style(), StyleVariant(), size_, children_copy,
-          padding_, contents_visual_rect_, baselines_copy, BoxType(),
+          padding_, contents_ink_overflow_, baselines_copy, BoxType(),
           is_old_layout_root_, border_edge_, break_token_));
   return physical_fragment;
 }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index 2f2ae61..a0c47ef 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -23,7 +23,7 @@
                         NGPhysicalSize size,
                         Vector<scoped_refptr<NGPhysicalFragment>>& children,
                         const NGPixelSnappedPhysicalBoxStrut& padding,
-                        const NGPhysicalOffsetRect& contents_visual_rect,
+                        const NGPhysicalOffsetRect& contents_ink_overflow,
                         Vector<NGBaseline>& baselines,
                         NGBoxType box_type,
                         bool is_old_layout_root,
@@ -55,10 +55,10 @@
 
   // Visual rect of this box in the local coordinate. Does not include children
   // even if they overflow this box.
-  NGPhysicalOffsetRect SelfVisualRect() const;
+  NGPhysicalOffsetRect SelfInkOverflow() const;
 
-  // VisualRect of itself including contents, in the local coordinate.
-  NGPhysicalOffsetRect VisualRectWithContents(bool clip_overflow) const;
+  // Ink overflow including contents, in the local coordinates.
+  NGPhysicalOffsetRect InkOverflow(bool apply_clip) const;
 
   void AddSelfOutlineRects(Vector<LayoutRect>*,
                            const LayoutPoint& additional_offset) const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
index 3c6fe5f..69deaa9d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
@@ -14,7 +14,7 @@
     NGFragmentType type,
     unsigned sub_type,
     Vector<scoped_refptr<NGPhysicalFragment>>& children,
-    const NGPhysicalOffsetRect& contents_visual_rect,
+    const NGPhysicalOffsetRect& contents_ink_overflow,
     scoped_refptr<NGBreakToken> break_token)
     : NGPhysicalFragment(layout_object,
                          style,
@@ -24,7 +24,7 @@
                          sub_type,
                          std::move(break_token)),
       children_(std::move(children)),
-      contents_visual_rect_(contents_visual_rect) {
+      contents_ink_overflow_(contents_ink_overflow) {
   DCHECK(children.IsEmpty());  // Ensure move semantics is used.
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h
index 4d80ac6..52da5f0 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h
@@ -18,9 +18,9 @@
     return children_;
   }
 
-  // Visual rect of children in the local coordinate.
-  const NGPhysicalOffsetRect& ContentsVisualRect() const {
-    return contents_visual_rect_;
+  // Ink overflow of children in local coordinates.
+  const NGPhysicalOffsetRect& ContentsInkOverflow() const {
+    return contents_ink_overflow_;
   }
 
  protected:
@@ -33,11 +33,11 @@
       NGFragmentType,
       unsigned sub_type,
       Vector<scoped_refptr<NGPhysicalFragment>>& children,
-      const NGPhysicalOffsetRect& contents_visual_rect,
+      const NGPhysicalOffsetRect& contents_ink_overflow,
       scoped_refptr<NGBreakToken> = nullptr);
 
   Vector<scoped_refptr<NGPhysicalFragment>> children_;
-  NGPhysicalOffsetRect contents_visual_rect_;
+  NGPhysicalOffsetRect contents_ink_overflow_;
 };
 
 DEFINE_TYPE_CASTS(NGPhysicalContainerFragment,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index 257d344..4c0c4c72 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -43,16 +43,16 @@
   if (flags & NGPhysicalFragment::DumpOverflow) {
     if (has_content)
       builder->Append(" ");
-    NGPhysicalOffsetRect overflow = fragment->VisualRectWithContents();
+    NGPhysicalOffsetRect overflow = fragment->InkOverflow();
     if (overflow.size.width != fragment->Size().width ||
         overflow.size.height != fragment->Size().height) {
-      builder->Append(" visualRectWithContents: ");
+      builder->Append(" InkOverflow: ");
       builder->Append(overflow.ToString());
       has_content = true;
     }
     if (has_content)
       builder->Append(" ");
-    overflow = fragment->SelfVisualRect();
+    overflow = fragment->SelfInkOverflow();
     if (overflow.size.width != fragment->Size().width ||
         overflow.size.height != fragment->Size().height) {
       builder->Append(" visualRect: ");
@@ -304,12 +304,12 @@
   return box_strut.SnapToDevicePixels();
 }
 
-NGPhysicalOffsetRect NGPhysicalFragment::SelfVisualRect() const {
+NGPhysicalOffsetRect NGPhysicalFragment::SelfInkOverflow() const {
   switch (Type()) {
     case NGPhysicalFragment::kFragmentBox:
-      return ToNGPhysicalBoxFragment(*this).SelfVisualRect();
+      return ToNGPhysicalBoxFragment(*this).SelfInkOverflow();
     case NGPhysicalFragment::kFragmentText:
-      return ToNGPhysicalTextFragment(*this).SelfVisualRect();
+      return ToNGPhysicalTextFragment(*this).SelfInkOverflow();
     case NGPhysicalFragment::kFragmentLineBox:
       return {{}, Size()};
   }
@@ -317,16 +317,14 @@
   return {{}, Size()};
 }
 
-NGPhysicalOffsetRect NGPhysicalFragment::VisualRectWithContents(
-    bool clip_overflow) const {
+NGPhysicalOffsetRect NGPhysicalFragment::InkOverflow(bool apply_clip) const {
   switch (Type()) {
     case NGPhysicalFragment::kFragmentBox:
-      return ToNGPhysicalBoxFragment(*this).VisualRectWithContents(
-          clip_overflow);
+      return ToNGPhysicalBoxFragment(*this).InkOverflow(apply_clip);
     case NGPhysicalFragment::kFragmentText:
-      return ToNGPhysicalTextFragment(*this).SelfVisualRect();
+      return ToNGPhysicalTextFragment(*this).SelfInkOverflow();
     case NGPhysicalFragment::kFragmentLineBox:
-      return ToNGPhysicalLineBoxFragment(*this).VisualRectWithContents();
+      return ToNGPhysicalLineBoxFragment(*this).InkOverflow();
   }
   NOTREACHED();
   return {{}, Size()};
@@ -345,11 +343,11 @@
   return {{}, Size()};
 }
 
-void NGPhysicalFragment::PropagateContentsVisualRect(
-    NGPhysicalOffsetRect* parent_visual_rect) const {
-  NGPhysicalOffsetRect visual_rect = VisualRectWithContents();
-  visual_rect.offset += Offset();
-  parent_visual_rect->Unite(visual_rect);
+void NGPhysicalFragment::PropagateContentsInkOverflow(
+    NGPhysicalOffsetRect* parent_ink_overflow) const {
+  NGPhysicalOffsetRect ink_overflow = InkOverflow();
+  ink_overflow.offset += Offset();
+  parent_ink_overflow->Unite(ink_overflow);
 }
 
 const Vector<NGInlineItem>& NGPhysicalFragment::InlineItemsOfContainingBlock()
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index 7b52e0a..fc7a496 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -169,17 +169,17 @@
   // with LegacyLayout.
   LayoutObject* GetLayoutObject() const { return layout_object_; }
 
-  // VisualRect of itself, not including contents, in the local coordinate.
-  NGPhysicalOffsetRect SelfVisualRect() const;
+  // InkOverflow of itself, not including contents, in the local coordinate.
+  NGPhysicalOffsetRect SelfInkOverflow() const;
 
-  // VisualRect of itself including contents, in the local coordinate.
-  NGPhysicalOffsetRect VisualRectWithContents(bool clip_overflow = true) const;
+  // InkOverflow of itself including contents, in the local coordinate.
+  NGPhysicalOffsetRect InkOverflow(bool apply_clip = true) const;
 
   // Scrollable overflow. including contents, in the local coordinate.
   NGPhysicalOffsetRect ScrollableOverflow() const;
 
   // Unite visual rect to propagate to parent's ContentsVisualRect.
-  void PropagateContentsVisualRect(NGPhysicalOffsetRect*) const;
+  void PropagateContentsInkOverflow(NGPhysicalOffsetRect*) const;
 
   // Should only be used by the parent fragment's layout.
   void SetOffset(NGPhysicalOffset offset) {
diff --git a/third_party/blink/renderer/core/paint/README.md b/third_party/blink/renderer/core/paint/README.md
index 0a3e2ed..8eddcab3 100644
--- a/third_party/blink/renderer/core/paint/README.md
+++ b/third_party/blink/renderer/core/paint/README.md
@@ -42,7 +42,7 @@
         elements because `z-index:auto` and `z-index:0` are considered equal for
         stacking context sorting and they may interleave by DOM order.
 
-        The difference of a stacked element of this type from a real stacking 
+        The difference of a stacked element of this type from a real stacking
         context is that it doesn't manage z-ordering of stacked descendants.
         These descendants are managed by the parent stacking context of this
         stacked element.
@@ -528,3 +528,25 @@
 We could update the `NeedsPaintPhaseXXX` flags in a separate tree walk, but that
 would regress performance of the first paint. For slimming paint v2, we can
 update the flags during the pre-painting tree walk to simplify the logics.
+
+### PaintNG
+
+[LayoutNG](../layout/ng/README.md]) is a project that will change how Layout
+generates geometry/style information for painting. Instead of modifying
+LayoutObjects, LayoutNG will generate an NGFragment tree.
+
+NGPaintFragments are:
+* immutable
+* all coordinates are physical. See
+[layout_box_model_object.h](../layout/layout_box_model_object.h).
+* instead of Location(), NGFragment has Offset(), a physical offset from parent
+fragment.
+
+The goal is for PaintNG to eventually paint from NGFragment tree,
+and not see LayoutObjects at all. Until this goal is reached,
+LegacyPaint, and NGPaint will coexist.
+
+When a particular LayoutObject subclass fully migrates to NG, its LayoutObject
+geometry information might no longer be updated\(\*\), and its
+painter needs to be rewritten to paint NGFragments.
+For example, see how BlockPainter is being rewritten as NGBoxFragmentPainter.
diff --git a/third_party/blink/renderer/core/paint/block_painter.cc b/third_party/blink/renderer/core/paint/block_painter.cc
index 1758d758..6da131da 100644
--- a/third_party/blink/renderer/core/paint/block_painter.cc
+++ b/third_party/blink/renderer/core/paint/block_painter.cc
@@ -196,7 +196,7 @@
         DisplayItem::kScrollHitTest);
     ScrollHitTestDisplayItem::Record(paint_info.context, layout_block_,
                                      DisplayItem::kScrollHitTest,
-                                     properties->ScrollTranslation());
+                                     *properties->ScrollTranslation());
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc
index cfa1245e..e8101ec0 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater.cc
@@ -43,9 +43,12 @@
   // that all of its ancestors have a visible subtree. An ancestor with no
   // visible subtree can be non-composited despite we expected it to, this
   // resulted in the paint offset used by CompositedLayerMapping to mismatch.
+#if 0
+  // TODO(crbug.com/838018): Re-enable this check.
   bool subpixel_accumulation_may_be_bogus = paint_layer->SubtreeIsInvisible();
   DCHECK(layout_snapped_paint_offset == snapped_paint_offset ||
          subpixel_accumulation_may_be_bogus);
+#endif
 
   base::Optional<PropertyTreeState> container_layer_state;
   auto SetContainerLayerState =
@@ -136,7 +139,7 @@
     state.SetClip(
         clipping_container
             ? clipping_container->FirstFragment().ContentsProperties().Clip()
-            : ClipPaintPropertyNode::Root());
+            : &ClipPaintPropertyNode::Root());
     squashing_layer->SetLayerState(
         state,
         snapped_paint_offset + mapping->SquashingLayerOffsetFromLayoutObject());
diff --git a/third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h b/third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h
index b75179c..b319147 100644
--- a/third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h
+++ b/third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h
@@ -56,7 +56,7 @@
   const FragmentData& fragment_data_;
   const bool& is_actually_needed_;
   LayoutPoint old_paint_offset_;
-  scoped_refptr<const TransformPaintPropertyNode> old_paint_offset_translation_;
+  std::unique_ptr<TransformPaintPropertyNode> old_paint_offset_translation_;
 };
 
 class FindVisualRectNeedingUpdateScopeBase {
diff --git a/third_party/blink/renderer/core/paint/fragment_data.h b/third_party/blink/renderer/core/paint/fragment_data.h
index 0d848d7..ea2a2d7c 100644
--- a/third_party/blink/renderer/core/paint/fragment_data.h
+++ b/third_party/blink/renderer/core/paint/fragment_data.h
@@ -7,7 +7,7 @@
 
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
-#include "third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h"
+#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
 
 namespace blink {
 
@@ -147,9 +147,9 @@
   //   node. Even though the div has no transform, its local border box
   //   properties would have a transform node that points to the div's
   //   ancestor transform space.
-  PropertyTreeState LocalBorderBoxProperties() const {
+  const PropertyTreeState& LocalBorderBoxProperties() const {
     DCHECK(HasLocalBorderBoxProperties());
-    return rare_data_->local_border_box_properties->GetPropertyTreeState();
+    return *rare_data_->local_border_box_properties;
   }
   bool HasLocalBorderBoxProperties() const {
     return rare_data_ && rare_data_->local_border_box_properties;
@@ -162,9 +162,9 @@
     EnsureRareData();
     if (!rare_data_->local_border_box_properties) {
       rare_data_->local_border_box_properties =
-          std::make_unique<RefCountedPropertyTreeState>(state);
+          std::make_unique<PropertyTreeState>(state);
     } else {
-      *rare_data_->local_border_box_properties = std::move(state);
+      *rare_data_->local_border_box_properties = state;
     }
   }
 
@@ -226,7 +226,7 @@
     LayoutPoint pagination_offset;
     LayoutUnit logical_top_in_flow_thread;
     std::unique_ptr<ObjectPaintProperties> paint_properties;
-    std::unique_ptr<RefCountedPropertyTreeState> local_border_box_properties;
+    std::unique_ptr<PropertyTreeState> local_border_box_properties;
     bool is_clip_path_cache_valid = false;
     base::Optional<IntRect> clip_path_bounding_box;
     scoped_refptr<const RefCountedPath> clip_path_path;
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 96a3e2a..445b189 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -256,7 +256,7 @@
 
   DCHECK(PhysicalFragment().ChildrenInline());
 
-  LayoutRect overflow_rect(box_fragment_.VisualContentsRect());
+  LayoutRect overflow_rect(box_fragment_.ChildrenInkOverflow());
   overflow_rect.MoveBy(paint_offset);
   if (!paint_info.GetCullRect().IntersectsCullRect(overflow_rect))
     return;
@@ -732,7 +732,7 @@
     const LayoutPoint& adjusted_paint_offset) const {
   // TODO(layout-dev): Add support for scrolling, see
   // BlockPainter::IntersectsPaintRect.
-  LayoutRect overflow_rect(box_fragment_.VisualOverflowRect());
+  LayoutRect overflow_rect(box_fragment_.SelfInkOverflow());
   overflow_rect.MoveBy(adjusted_paint_offset);
   return paint_info.GetCullRect().IntersectsCullRect(overflow_rect);
 }
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
index f04af214..ff7605d9 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
@@ -169,12 +169,12 @@
          ToNGPhysicalBoxFragment(*physical_fragment_).ShouldClipOverflow();
 }
 
-LayoutRect NGPaintFragment::VisualOverflowRect() const {
-  return physical_fragment_->VisualRectWithContents().ToLayoutRect();
+LayoutRect NGPaintFragment::SelfInkOverflow() const {
+  return physical_fragment_->InkOverflow().ToLayoutRect();
 }
 
-LayoutRect NGPaintFragment::VisualContentsRect() const {
-  return physical_fragment_->VisualRectWithContents(false).ToLayoutRect();
+LayoutRect NGPaintFragment::ChildrenInkOverflow() const {
+  return physical_fragment_->InkOverflow(false).ToLayoutRect();
 }
 
 // Populate descendants from NGPhysicalFragment tree.
@@ -271,7 +271,7 @@
 
   for (NGPaintFragment* fragment : fragments) {
     NGPhysicalOffsetRect child_visual_rect =
-        fragment->PhysicalFragment().SelfVisualRect();
+        fragment->PhysicalFragment().SelfInkOverflow();
     child_visual_rect.offset += fragment->InlineOffsetToContainerBox();
     visual_rect->Unite(child_visual_rect.ToLayoutRect());
   }
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
index e21b4dd..b5a07db 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
@@ -74,13 +74,15 @@
   bool HasOverflowClip() const;
   bool ShouldClipOverflow() const;
   bool HasSelfPaintingLayer() const;
+  // This is equivalent to LayoutObject::VisualRect
   LayoutRect VisualRect() const override { return visual_rect_; }
   void SetVisualRect(const LayoutRect& rect) { visual_rect_ = rect; }
+
   // CSS ink overflow https://www.w3.org/TR/css-overflow-3/#ink
   // Encloses all pixels painted by self + children.
-  LayoutRect VisualOverflowRect() const;
+  LayoutRect SelfInkOverflow() const;
   // Union of children's ink overflows.
-  LayoutRect VisualContentsRect() const;
+  LayoutRect ChildrenInkOverflow() const;
 
   LayoutRect PartialInvalidationRect() const override;
 
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties.h b/third_party/blink/renderer/core/paint/object_paint_properties.h
index 016233a..c0bc162 100644
--- a/third_party/blink/renderer/core/paint/object_paint_properties.h
+++ b/third_party/blink/renderer/core/paint/object_paint_properties.h
@@ -188,98 +188,88 @@
   };
 
   UpdateResult UpdatePaintOffsetTranslation(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
+      const TransformPaintPropertyNode& parent,
       TransformPaintPropertyNode::State&& state) {
-    return Update(paint_offset_translation_, std::move(parent),
-                  std::move(state));
+    return Update(paint_offset_translation_, parent, std::move(state));
   }
-  UpdateResult UpdateTransform(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
-      TransformPaintPropertyNode::State&& state) {
-    return Update(transform_, std::move(parent), std::move(state));
+  UpdateResult UpdateTransform(const TransformPaintPropertyNode& parent,
+                               TransformPaintPropertyNode::State&& state) {
+    return Update(transform_, parent, std::move(state));
   }
-  UpdateResult UpdatePerspective(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
-      TransformPaintPropertyNode::State&& state) {
-    return Update(perspective_, std::move(parent), std::move(state));
+  UpdateResult UpdatePerspective(const TransformPaintPropertyNode& parent,
+                                 TransformPaintPropertyNode::State&& state) {
+    return Update(perspective_, parent, std::move(state));
   }
   UpdateResult UpdateSvgLocalToBorderBoxTransform(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
+      const TransformPaintPropertyNode& parent,
       TransformPaintPropertyNode::State&& state) {
     DCHECK(!ScrollTranslation()) << "SVG elements cannot scroll so there "
                                     "should never be both a scroll translation "
                                     "and an SVG local to border box transform.";
-    return Update(svg_local_to_border_box_transform_, std::move(parent),
-                  std::move(state));
+    return Update(svg_local_to_border_box_transform_, parent, std::move(state));
   }
-  UpdateResult UpdateScroll(scoped_refptr<const ScrollPaintPropertyNode> parent,
+  UpdateResult UpdateScroll(const ScrollPaintPropertyNode& parent,
                             ScrollPaintPropertyNode::State&& state) {
-    return Update(scroll_, std::move(parent), std::move(state));
+    return Update(scroll_, parent, std::move(state));
   }
   UpdateResult UpdateScrollTranslation(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
+      const TransformPaintPropertyNode& parent,
       TransformPaintPropertyNode::State&& state) {
     DCHECK(!SvgLocalToBorderBoxTransform())
         << "SVG elements cannot scroll so there should never be both a scroll "
            "translation and an SVG local to border box transform.";
-    return Update(scroll_translation_, std::move(parent), std::move(state));
+    return Update(scroll_translation_, parent, std::move(state));
   }
-  UpdateResult UpdateEffect(scoped_refptr<const EffectPaintPropertyNode> parent,
+  UpdateResult UpdateEffect(const EffectPaintPropertyNode& parent,
                             EffectPaintPropertyNode::State&& state) {
-    return Update(effect_, std::move(parent), std::move(state));
+    return Update(effect_, parent, std::move(state));
   }
-  UpdateResult UpdateFilter(scoped_refptr<const EffectPaintPropertyNode> parent,
+  UpdateResult UpdateFilter(const EffectPaintPropertyNode& parent,
                             EffectPaintPropertyNode::State&& state) {
-    return Update(filter_, std::move(parent), std::move(state));
+    return Update(filter_, parent, std::move(state));
   }
-  UpdateResult UpdateMask(scoped_refptr<const EffectPaintPropertyNode> parent,
+  UpdateResult UpdateMask(const EffectPaintPropertyNode& parent,
                           EffectPaintPropertyNode::State&& state) {
-    return Update(mask_, std::move(parent), std::move(state));
+    return Update(mask_, parent, std::move(state));
   }
-  UpdateResult UpdateClipPath(
-      scoped_refptr<const EffectPaintPropertyNode> parent,
-      EffectPaintPropertyNode::State&& state) {
-    return Update(clip_path_, std::move(parent), std::move(state));
+  UpdateResult UpdateClipPath(const EffectPaintPropertyNode& parent,
+                              EffectPaintPropertyNode::State&& state) {
+    return Update(clip_path_, parent, std::move(state));
   }
-  UpdateResult UpdateFragmentClip(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
-      ClipPaintPropertyNode::State&& state) {
-    return Update(fragment_clip_, std::move(parent), std::move(state));
+  UpdateResult UpdateFragmentClip(const ClipPaintPropertyNode& parent,
+                                  ClipPaintPropertyNode::State&& state) {
+    return Update(fragment_clip_, parent, std::move(state));
   }
-  UpdateResult UpdateClipPathClip(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
-      ClipPaintPropertyNode::State&& state) {
-    return Update(clip_path_clip_, std::move(parent), std::move(state));
+  UpdateResult UpdateClipPathClip(const ClipPaintPropertyNode& parent,
+                                  ClipPaintPropertyNode::State&& state) {
+    return Update(clip_path_clip_, parent, std::move(state));
   }
-  UpdateResult UpdateMaskClip(scoped_refptr<const ClipPaintPropertyNode> parent,
+  UpdateResult UpdateMaskClip(const ClipPaintPropertyNode& parent,
                               ClipPaintPropertyNode::State&& state) {
-    return Update(mask_clip_, std::move(parent), std::move(state));
+    return Update(mask_clip_, parent, std::move(state));
   }
-  UpdateResult UpdateCssClip(scoped_refptr<const ClipPaintPropertyNode> parent,
+  UpdateResult UpdateCssClip(const ClipPaintPropertyNode& parent,
                              ClipPaintPropertyNode::State&& state) {
-    return Update(css_clip_, std::move(parent), std::move(state));
+    return Update(css_clip_, parent, std::move(state));
   }
   UpdateResult UpdateCssClipFixedPosition(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
+      const ClipPaintPropertyNode& parent,
       ClipPaintPropertyNode::State&& state) {
-    return Update(css_clip_fixed_position_, std::move(parent),
-                  std::move(state));
+    return Update(css_clip_fixed_position_, parent, std::move(state));
   }
   UpdateResult UpdateOverflowControlsClip(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
+      const ClipPaintPropertyNode& parent,
       ClipPaintPropertyNode::State&& state) {
-    return Update(overflow_controls_clip_, std::move(parent), std::move(state));
+    return Update(overflow_controls_clip_, parent, std::move(state));
   }
   UpdateResult UpdateInnerBorderRadiusClip(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
+      const ClipPaintPropertyNode& parent,
       ClipPaintPropertyNode::State&& state) {
-    return Update(inner_border_radius_clip_, std::move(parent),
-                  std::move(state));
+    return Update(inner_border_radius_clip_, parent, std::move(state));
   }
-  UpdateResult UpdateOverflowClip(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
-      ClipPaintPropertyNode::State&& state) {
-    return Update(overflow_clip_, std::move(parent), std::move(state));
+  UpdateResult UpdateOverflowClip(const ClipPaintPropertyNode& parent,
+                                  ClipPaintPropertyNode::State&& state) {
+    return Update(overflow_clip_, parent, std::move(state));
   }
 
 #if DCHECK_IS_ON()
@@ -335,7 +325,7 @@
   // deleted), and false otherwise. See the class-level comment ("update & clear
   // implementation note") for details about why this is needed for efficiency.
   template <typename PaintPropertyNode>
-  bool Clear(scoped_refptr<PaintPropertyNode>& field) {
+  bool Clear(std::unique_ptr<PaintPropertyNode>& field) {
     if (field) {
       field = nullptr;
       return true;
@@ -347,39 +337,40 @@
   // created), and false otherwise. See the class-level comment ("update & clear
   // implementation note") for details about why this is needed for efficiency.
   template <typename PaintPropertyNode>
-  UpdateResult Update(scoped_refptr<PaintPropertyNode>& field,
-                      scoped_refptr<const PaintPropertyNode> parent,
+  UpdateResult Update(std::unique_ptr<PaintPropertyNode>& field,
+                      const PaintPropertyNode& parent,
                       typename PaintPropertyNode::State&& state) {
     if (field) {
-      return field->Update(std::move(parent), std::move(state))
+      return field->Update(parent, std::move(state))
                  ? UpdateResult::kValueChanged
                  : UpdateResult::kUnchanged;
     }
-    field = PaintPropertyNode::Create(std::move(parent), std::move(state));
+    field = PaintPropertyNode::Create(parent, std::move(state));
     return UpdateResult::kNewNodeCreated;
   }
 
   // ATTENTION! Make sure to keep FindPropertiesNeedingUpdate.h in sync when
   // new properites are added!
-  scoped_refptr<TransformPaintPropertyNode> paint_offset_translation_;
-  scoped_refptr<TransformPaintPropertyNode> transform_;
-  scoped_refptr<EffectPaintPropertyNode> effect_;
-  scoped_refptr<EffectPaintPropertyNode> filter_;
-  scoped_refptr<EffectPaintPropertyNode> mask_;
-  scoped_refptr<EffectPaintPropertyNode> clip_path_;
-  scoped_refptr<ClipPaintPropertyNode> fragment_clip_;
-  scoped_refptr<ClipPaintPropertyNode> clip_path_clip_;
-  scoped_refptr<ClipPaintPropertyNode> mask_clip_;
-  scoped_refptr<ClipPaintPropertyNode> css_clip_;
-  scoped_refptr<ClipPaintPropertyNode> css_clip_fixed_position_;
-  scoped_refptr<ClipPaintPropertyNode> overflow_controls_clip_;
-  scoped_refptr<ClipPaintPropertyNode> inner_border_radius_clip_;
-  scoped_refptr<ClipPaintPropertyNode> overflow_clip_;
-  scoped_refptr<TransformPaintPropertyNode> perspective_;
+  std::unique_ptr<TransformPaintPropertyNode> paint_offset_translation_;
+  std::unique_ptr<TransformPaintPropertyNode> transform_;
+  std::unique_ptr<EffectPaintPropertyNode> effect_;
+  std::unique_ptr<EffectPaintPropertyNode> filter_;
+  std::unique_ptr<EffectPaintPropertyNode> mask_;
+  std::unique_ptr<EffectPaintPropertyNode> clip_path_;
+  std::unique_ptr<ClipPaintPropertyNode> fragment_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> clip_path_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> mask_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> css_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> css_clip_fixed_position_;
+  std::unique_ptr<ClipPaintPropertyNode> overflow_controls_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> inner_border_radius_clip_;
+  std::unique_ptr<ClipPaintPropertyNode> overflow_clip_;
+  std::unique_ptr<TransformPaintPropertyNode> perspective_;
   // TODO(pdr): Only LayoutSVGRoot needs this and it should be moved there.
-  scoped_refptr<TransformPaintPropertyNode> svg_local_to_border_box_transform_;
-  scoped_refptr<ScrollPaintPropertyNode> scroll_;
-  scoped_refptr<TransformPaintPropertyNode> scroll_translation_;
+  std::unique_ptr<TransformPaintPropertyNode>
+      svg_local_to_border_box_transform_;
+  std::unique_ptr<ScrollPaintPropertyNode> scroll_;
+  std::unique_ptr<TransformPaintPropertyNode> scroll_translation_;
 
   DISALLOW_COPY_AND_ASSIGN(ObjectPaintProperties);
 };
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 97e5ae81..4b84092b 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -258,7 +258,7 @@
 // We need update |fragment| visual rect considering selection.
 static LayoutRect ComputeLocalVisualRect(const NGPaintFragment& fragment) {
   const NGPhysicalFragment& physical_fragment = fragment.PhysicalFragment();
-  LayoutRect rect = physical_fragment.VisualRectWithContents().ToLayoutRect();
+  LayoutRect rect = physical_fragment.InkOverflow().ToLayoutRect();
   if (!physical_fragment.IsText())
     return rect;
   const FrameSelection& frame_selection =
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 79c7b83f..4be475bfc 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -39,13 +39,13 @@
 
 PaintPropertyTreeBuilderFragmentContext::
     PaintPropertyTreeBuilderFragmentContext()
-    : current_effect(EffectPaintPropertyNode::Root()) {
+    : current_effect(&EffectPaintPropertyNode::Root()) {
   current.clip = absolute_position.clip = fixed_position.clip =
-      ClipPaintPropertyNode::Root();
+      &ClipPaintPropertyNode::Root();
   current.transform = absolute_position.transform = fixed_position.transform =
-      TransformPaintPropertyNode::Root();
+      &TransformPaintPropertyNode::Root();
   current.scroll = absolute_position.scroll = fixed_position.scroll =
-      ScrollPaintPropertyNode::Root();
+      &ScrollPaintPropertyNode::Root();
 }
 
 void PaintPropertyTreeBuilder::SetupContextForFrame(
@@ -295,7 +295,7 @@
         RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
       state.rendering_context_id = context_.current.rendering_context_id;
     OnUpdate(properties_->UpdatePaintOffsetTranslation(
-        context_.current.transform, std::move(state)));
+        *context_.current.transform, std::move(state)));
     context_.current.transform = properties_->PaintOffsetTranslation();
     if (object_.IsLayoutView()) {
       context_.absolute_position.transform =
@@ -331,7 +331,7 @@
     if (NeedsTransformForNonRootSVG(object_)) {
       // The origin is included in the local transform, so leave origin empty.
       OnUpdate(properties_->UpdateTransform(
-          context_.current.transform,
+          *context_.current.transform,
           TransformPaintPropertyNode::State{transform}));
     } else {
       OnClear(properties_->ClearTransform());
@@ -449,7 +449,7 @@
             object_.UniqueId(), CompositorElementIdNamespace::kPrimary);
       }
 
-      OnUpdate(properties_->UpdateTransform(context_.current.transform,
+      OnUpdate(properties_->UpdateTransform(*context_.current.transform,
                                             std::move(state)));
     } else {
       OnClear(properties_->ClearTransform());
@@ -578,7 +578,7 @@
           combined_clip.Intersect(*clip_path_clip);
 
         OnUpdateClip(properties_->UpdateMaskClip(
-            context_.current.clip,
+            *context_.current.clip,
             ClipPaintPropertyNode::State{context_.current.transform,
                                          FloatRoundedRect(combined_clip)}));
         output_clip = properties_->MaskClip();
@@ -607,8 +607,8 @@
         state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
             object_.UniqueId(), CompositorElementIdNamespace::kPrimary);
       }
-      OnUpdate(
-          properties_->UpdateEffect(context_.current_effect, std::move(state)));
+      OnUpdate(properties_->UpdateEffect(*context_.current_effect,
+                                         std::move(state)));
 
       if (mask_clip || has_spv1_composited_clip_path) {
         EffectPaintPropertyNode::State mask_state;
@@ -623,16 +623,16 @@
                   object_.UniqueId(),
                   CompositorElementIdNamespace::kEffectMask);
         }
-        OnUpdate(properties_->UpdateMask(properties_->Effect(),
+        OnUpdate(properties_->UpdateMask(*properties_->Effect(),
                                          std::move(mask_state)));
       } else {
         OnClear(properties_->ClearMask());
       }
 
       if (has_mask_based_clip_path) {
-        const EffectPaintPropertyNode* parent = has_spv1_composited_clip_path
-                                                    ? properties_->Mask()
-                                                    : properties_->Effect();
+        const EffectPaintPropertyNode& parent = has_spv1_composited_clip_path
+                                                    ? *properties_->Mask()
+                                                    : *properties_->Effect();
         EffectPaintPropertyNode::State clip_path_state;
         clip_path_state.local_transform_space = context_.current.transform;
         clip_path_state.output_clip = output_clip;
@@ -746,8 +746,8 @@
             object_.UniqueId(), CompositorElementIdNamespace::kEffectFilter);
       }
 
-      OnUpdate(
-          properties_->UpdateFilter(context_.current_effect, std::move(state)));
+      OnUpdate(properties_->UpdateFilter(*context_.current_effect,
+                                         std::move(state)));
     } else {
       OnClear(properties_->ClearFilter());
     }
@@ -775,7 +775,7 @@
   if (NeedsPaintPropertyUpdate()) {
     if (context_.fragment_clip) {
       OnUpdateClip(properties_->UpdateFragmentClip(
-          context_.current.clip,
+          *context_.current.clip,
           ClipPaintPropertyNode::State{context_.current.transform,
                                        ToClipRect(*context_.fragment_clip)}));
     } else {
@@ -802,7 +802,7 @@
       // copy from in-flow context later at updateOutOfFlowContext() step.
       DCHECK(object_.CanContainAbsolutePositionObjects());
       OnUpdateClip(properties_->UpdateCssClip(
-          context_.current.clip,
+          *context_.current.clip,
           ClipPaintPropertyNode::State{context_.current.transform,
                                        ToClipRect(ToLayoutBox(object_).ClipRect(
                                            context_.current.paint_offset))}));
@@ -835,7 +835,7 @@
       state.clip_rect =
           FloatRoundedRect(FloatRect(*fragment_data_.ClipPathBoundingBox()));
       state.clip_path = fragment_data_.ClipPathPath();
-      OnUpdateClip(properties_->UpdateClipPathClip(context_.current.clip,
+      OnUpdateClip(properties_->UpdateClipPathClip(*context_.current.clip,
                                                    std::move(state)));
     }
   }
@@ -964,7 +964,7 @@
     // Clip overflow controls to the border box rect. Not wrapped with
     // OnUpdateClip() because this clip doesn't affect descendants.
     properties_->UpdateOverflowControlsClip(
-        context_.current.clip,
+        *context_.current.clip,
         ClipPaintPropertyNode::State{
             context_.current.transform,
             ToClipRect(LayoutRect(context_.current.paint_offset,
@@ -1000,7 +1000,7 @@
             LayoutRect(context_.current.paint_offset, box.Size()));
       }
       OnUpdateClip(properties_->UpdateInnerBorderRadiusClip(
-          context_.current.clip, std::move(state)));
+          *context_.current.clip, std::move(state)));
     } else {
       OnClearClip(properties_->ClearInnerBorderRadiusClip());
     }
@@ -1071,7 +1071,7 @@
       bool equal_ignoring_hit_test_rects =
           !!existing &&
           existing->EqualIgnoringHitTestRects(context_.current.clip, state);
-      OnUpdateClip(properties_->UpdateOverflowClip(context_.current.clip,
+      OnUpdateClip(properties_->UpdateOverflowClip(*context_.current.clip,
                                                    std::move(state)),
                    equal_ignoring_hit_test_rects);
     } else {
@@ -1113,7 +1113,7 @@
       if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
           RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
         state.rendering_context_id = context_.current.rendering_context_id;
-      OnUpdate(properties_->UpdatePerspective(context_.current.transform,
+      OnUpdate(properties_->UpdatePerspective(*context_.current.transform,
                                               std::move(state)));
     } else {
       OnClear(properties_->ClearPerspective());
@@ -1138,7 +1138,7 @@
     if (!transform_to_border_box.IsIdentity() &&
         NeedsSVGLocalToBorderBoxTransform(object_)) {
       OnUpdate(properties_->UpdateSvgLocalToBorderBoxTransform(
-          context_.current.transform,
+          *context_.current.transform,
           TransformPaintPropertyNode::State{transform_to_border_box}));
     } else {
       OnClear(properties_->ClearSvgLocalToBorderBoxTransform());
@@ -1220,8 +1220,8 @@
           RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
         state.compositor_element_id = scrollable_area->GetCompositorElementId();
 
-      OnUpdate(
-          properties_->UpdateScroll(context_.current.scroll, std::move(state)));
+      OnUpdate(properties_->UpdateScroll(*context_.current.scroll,
+                                         std::move(state)));
     } else {
       OnClear(properties_->ClearScroll());
     }
@@ -1241,7 +1241,7 @@
         state.rendering_context_id = context_.current.rendering_context_id;
       }
       state.scroll = properties_->Scroll();
-      OnUpdate(properties_->UpdateScrollTranslation(context_.current.transform,
+      OnUpdate(properties_->UpdateScrollTranslation(*context_.current.transform,
                                                     std::move(state)));
     } else {
       OnClear(properties_->ClearScrollTranslation());
@@ -1296,7 +1296,7 @@
     } else {
       if (NeedsPaintPropertyUpdate()) {
         OnUpdate(properties_->UpdateCssClipFixedPosition(
-            context_.fixed_position.clip,
+            *context_.fixed_position.clip,
             ClipPaintPropertyNode::State{css_clip->LocalTransformSpace(),
                                          css_clip->ClipRect()}));
       }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 423e205..a5e8279 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -845,7 +845,7 @@
       PaintPropertiesForElement("groupWithOpacity");
   EXPECT_EQ(0.6f, group_with_opacity_properties->Effect()->Opacity());
   EXPECT_EQ(svg_clip, group_with_opacity_properties->Effect()->OutputClip());
-  EXPECT_EQ(EffectPaintPropertyNode::Root(),
+  EXPECT_EQ(&EffectPaintPropertyNode::Root(),
             group_with_opacity_properties->Effect()->Parent());
 
   EXPECT_EQ(nullptr, PaintPropertiesForElement("rectWithoutOpacity"));
@@ -4626,7 +4626,7 @@
 
   // The <marker> object resets to a new paint property tree, so the
   // transform within it should have the root as parent.
-  EXPECT_EQ(TransformPaintPropertyNode::Root(),
+  EXPECT_EQ(&TransformPaintPropertyNode::Root(),
             transform_inside_marker_properties->Transform()->Parent());
 
   // Whereas this is not true of the transform above the path.
@@ -4659,7 +4659,7 @@
 
   // The <marker> object resets to a new paint property tree, so the
   // transform within it should have the root as parent.
-  EXPECT_EQ(TransformPaintPropertyNode::Root(),
+  EXPECT_EQ(&TransformPaintPropertyNode::Root(),
             transform_inside_symbol_properties->Transform()->Parent());
 
   // Whereas this is not true of the transform above the path.
@@ -4680,7 +4680,7 @@
   const ObjectPaintProperties* svg_root_properties =
       svg_root.FirstFragment().PaintProperties();
   EXPECT_TRUE(svg_root_properties->Effect());
-  EXPECT_EQ(EffectPaintPropertyNode::Root(),
+  EXPECT_EQ(&EffectPaintPropertyNode::Root(),
             svg_root_properties->Effect()->Parent());
 }
 
diff --git a/third_party/blink/renderer/core/paint/view_painter_test.cc b/third_party/blink/renderer/core/paint/view_painter_test.cc
index bd160ca6..cbd6c70 100644
--- a/third_party/blink/renderer/core/paint/view_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/view_painter_test.cc
@@ -116,7 +116,7 @@
   EXPECT_EQ(background_chunk_client, &chunk.id.client);
 
   const auto& tree_state = chunk.properties;
-  EXPECT_EQ(EffectPaintPropertyNode::Root(), tree_state.Effect());
+  EXPECT_EQ(&EffectPaintPropertyNode::Root(), tree_state.Effect());
   const auto* properties = GetLayoutView().FirstFragment().PaintProperties();
   EXPECT_EQ(properties->ScrollTranslation(), tree_state.Transform());
   EXPECT_EQ(properties->OverflowClip(), tree_state.Clip());
diff --git a/third_party/blink/renderer/core/workers/worklet.idl b/third_party/blink/renderer/core/workers/worklet.idl
index bc6590c..018d604 100644
--- a/third_party/blink/renderer/core/workers/worklet.idl
+++ b/third_party/blink/renderer/core/workers/worklet.idl
@@ -5,7 +5,6 @@
 // https://drafts.css-houdini.org/worklets/#worklet
 
 [
-    RuntimeEnabled=Worklet,
     SecureContext
 ] interface Worklet {
     [CallWith=ScriptState] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options);
diff --git a/third_party/blink/renderer/core/workers/worklet_global_scope.idl b/third_party/blink/renderer/core/workers/worklet_global_scope.idl
index 376b3301..c641fe8 100644
--- a/third_party/blink/renderer/core/workers/worklet_global_scope.idl
+++ b/third_party/blink/renderer/core/workers/worklet_global_scope.idl
@@ -7,7 +7,6 @@
 [
     ActiveScriptWrappable,
     Exposed=Worklet,
-    RuntimeEnabled=Worklet,
     ImmutablePrototype
 ] interface WorkletGlobalScope {
 };
diff --git a/third_party/blink/renderer/devtools/front_end/ui/Fragment.js b/third_party/blink/renderer/devtools/front_end/ui/Fragment.js
index b7aabfb6..cbc7ecd 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/Fragment.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/Fragment.js
@@ -163,15 +163,7 @@
         result._elementsById.set(/** @type {string} */ (bind.elementId), element);
       } else if ('replaceNodeIndex' in bind) {
         const value = values[/** @type {number} */ (bind.replaceNodeIndex)];
-        let node = null;
-        if (value instanceof Node)
-          node = value;
-        else if (value instanceof UI.Fragment)
-          node = value._element;
-        else
-          node = createTextNode('' + value);
-
-        element.parentNode.replaceChild(node, element);
+        element.parentNode.replaceChild(this._nodeForValue(value), element);
       } else if ('attr' in bind) {
         if (bind.attr.names.length === 2 && bind.attr.values.length === 1 &&
             typeof values[bind.attr.index] === 'function') {
@@ -197,6 +189,24 @@
     }
     return result;
   }
+
+  /**
+   * @param {*} value
+   * @return {!Node}
+   */
+  static _nodeForValue(value) {
+    if (value instanceof Node)
+      return value;
+    if (value instanceof UI.Fragment)
+      return value._element;
+    if (Array.isArray(value)) {
+      const node = createDocumentFragment();
+      for (const v of value)
+        node.appendChild(this._nodeForValue(v));
+      return node;
+    }
+    return createTextNode('' + value);
+  }
 };
 
 /**
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 938be70..1656aa1 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -32,7 +32,6 @@
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/graphics/stroke_data.h"
 #include "third_party/blink/renderer/platform/histogram.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/checked_numeric.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
@@ -938,10 +937,7 @@
     const CanvasImageSourceUnion& value,
     ExceptionState& exception_state) {
   if (value.IsCSSImageValue()) {
-    if (RuntimeEnabledFeatures::CSSPaintAPIEnabled())
-      return value.GetAsCSSImageValue();
-    exception_state.ThrowTypeError("CSSImageValue is not yet supported");
-    return nullptr;
+    return value.GetAsCSSImageValue();
   }
   if (value.IsHTMLImageElement())
     return value.GetAsHTMLImageElement();
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
index 29cea1a..047b020 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
@@ -32,7 +32,7 @@
     Constructor,
     Constructor(Path2D path),
     Constructor(DOMString text),
-    Exposed(Worker OffscreenCanvas, Window StableBlinkFeatures, PaintWorklet CSSPaintAPI)
+    Exposed(Worker OffscreenCanvas, Window StableBlinkFeatures, PaintWorklet StableBlinkFeatures)
 ] interface Path2D {
 
     void addPath(Path2D path, optional DOMMatrix2DInit transform);
diff --git a/third_party/blink/renderer/modules/csspaint/css_paint_worklet.idl b/third_party/blink/renderer/modules/csspaint/css_paint_worklet.idl
index 17b1ae5..bf14fcc6 100644
--- a/third_party/blink/renderer/modules/csspaint/css_paint_worklet.idl
+++ b/third_party/blink/renderer/modules/csspaint/css_paint_worklet.idl
@@ -6,7 +6,6 @@
 
 [
     ImplementedAs=CSSPaintWorklet,
-    RuntimeEnabled=CSSPaintAPI,
     SecureContext
 ] partial interface CSS {
     [CallWith=ScriptState,MeasureAs=PaintWorklet] static readonly attribute Worklet paintWorklet;
diff --git a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
index d889355..7d69e76 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.idl
@@ -5,8 +5,7 @@
 // https://drafts.css-houdini.org/css-paint-api/#paintrenderingcontext2d
 
 [
-    Exposed=PaintWorklet,
-    RuntimeEnabled=CSSPaintAPI
+    Exposed=PaintWorklet
 ] interface PaintRenderingContext2D {
     // state
     void save(); // push state on state stack
diff --git a/third_party/blink/renderer/modules/csspaint/paint_size.idl b/third_party/blink/renderer/modules/csspaint/paint_size.idl
index d7d9a81..1e57ff8c 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_size.idl
+++ b/third_party/blink/renderer/modules/csspaint/paint_size.idl
@@ -5,8 +5,7 @@
 // https://drafts.css-houdini.org/css-paint-api/#paintworkletglobalscope
 
 [
-    Exposed=PaintWorklet,
-    RuntimeEnabled=CSSPaintAPI
+    Exposed=PaintWorklet
 ] interface PaintSize {
     readonly attribute double width;
     readonly attribute double height;
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.idl b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.idl
index 256194c..cef58d7 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.idl
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.idl
@@ -6,8 +6,7 @@
 
 [
     Exposed=PaintWorklet,
-    Global=(Worklet,PaintWorklet),
-    RuntimeEnabled=CSSPaintAPI
+    Global=(Worklet,PaintWorklet)
 ] interface PaintWorkletGlobalScope : WorkletGlobalScope {
     [Measure] readonly attribute unrestricted double devicePixelRatio;
     [Measure, RaisesException] void registerPaint(DOMString name, Function paintCtor);
diff --git a/third_party/blink/renderer/modules/payments/can_make_payment_event.cc b/third_party/blink/renderer/modules/payments/can_make_payment_event.cc
index b3468af1..1cebb50 100644
--- a/third_party/blink/renderer/modules/payments/can_make_payment_event.cc
+++ b/third_party/blink/renderer/modules/payments/can_make_payment_event.cc
@@ -85,8 +85,12 @@
     : ExtendableEvent(type, initializer, wait_until_observer),
       top_origin_(initializer.topOrigin()),
       payment_request_origin_(initializer.paymentRequestOrigin()),
-      method_data_(std::move(initializer.methodData())),
-      modifiers_(initializer.modifiers()),
+      method_data_(initializer.hasMethodData()
+                       ? initializer.methodData()
+                       : HeapVector<PaymentMethodData>()),
+      modifiers_(initializer.hasModifiers()
+                     ? initializer.modifiers()
+                     : HeapVector<PaymentDetailsModifier>()),
       observer_(respond_with_observer) {}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/payments/payment_request_event.cc b/third_party/blink/renderer/modules/payments/payment_request_event.cc
index 0f97bd0d..6b09da7c 100644
--- a/third_party/blink/renderer/modules/payments/payment_request_event.cc
+++ b/third_party/blink/renderer/modules/payments/payment_request_event.cc
@@ -140,9 +140,14 @@
       top_origin_(initializer.topOrigin()),
       payment_request_origin_(initializer.paymentRequestOrigin()),
       payment_request_id_(initializer.paymentRequestId()),
-      method_data_(std::move(initializer.methodData())),
-      total_(initializer.total()),
-      modifiers_(initializer.modifiers()),
+      method_data_(initializer.hasMethodData()
+                       ? initializer.methodData()
+                       : HeapVector<PaymentMethodData>()),
+      total_(initializer.hasTotal() ? initializer.total()
+                                    : PaymentCurrencyAmount()),
+      modifiers_(initializer.hasModifiers()
+                     ? initializer.modifiers()
+                     : HeapVector<PaymentDetailsModifier>()),
       instrument_key_(initializer.instrumentKey()),
       observer_(respond_with_observer) {}
 
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
index 94c3d66..4aefafe 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
@@ -43,9 +43,6 @@
   // request Picture-in-Picture.
   Status IsDocumentAllowed() const;
 
-  // Enter Picture-in-Picture for a video element and resolve promise.
-  void EnterPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*);
-
   // Meant to be called internally when an element has entered successfully
   // Picture-in-Picture.
   void OnEnteredPictureInPicture(HTMLVideoElement*,
@@ -59,6 +56,8 @@
   Element* PictureInPictureElement(TreeScope&) const;
 
   // Implementation of PictureInPictureController.
+  void EnterPictureInPicture(HTMLVideoElement*,
+                             ScriptPromiseResolver*) override;
   void OnExitedPictureInPicture(ScriptPromiseResolver*) override;
   Status IsElementAllowed(const HTMLVideoElement&) const override;
   bool IsPictureInPictureElement(const Element*) const override;
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 917d166..f3f0bc4 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1061,8 +1061,6 @@
     "graphics/paint/property_tree_state.h",
     "graphics/paint/raster_invalidation_tracking.cc",
     "graphics/paint/raster_invalidation_tracking.h",
-    "graphics/paint/ref_counted_property_tree_state.cc",
-    "graphics/paint/ref_counted_property_tree_state.h",
     "graphics/paint/scoped_display_item_fragment.h",
     "graphics/paint/scoped_paint_chunk_properties.h",
     "graphics/paint/scroll_display_item.cc",
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
index 8cf33c60..ee936ab 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -114,34 +114,20 @@
     DCHECK(end > start);
     unsigned number_of_characters = std::min(end - start, num_characters_);
 
-    // This ends up looping over the glyphs twice if we don't know the glyph
-    // count up front. Once to count the number of glyphs and allocate the new
-    // RunInfo object and then a second time to copy the glyphs over.
-    // TODO: Compared to the cost of allocation and copying the extra loop is
-    // probably fine but we might want to try to eliminate it if we can.
-    unsigned number_of_glyphs;
-    if (start == 0 && end == num_characters_) {
-      number_of_glyphs = glyph_data_.size();
-    } else {
-      number_of_glyphs = 0;
-      ForEachGlyphInRange(
-          0, start_index_ + start, start_index_ + end, 0,
-          [&](const HarfBuzzRunGlyphData&, float, uint16_t) -> bool {
-            number_of_glyphs++;
-            return true;
-          });
-    }
-
+    // This is incorrect but is a resonable guess for the initial size of the
+    // glyph_data_ vector. It'll be resized once we've determined the number of
+    // glyphs in the sub run.
+    unsigned number_of_glyphs = number_of_characters;
     auto run = std::make_unique<RunInfo>(
         font_data_.get(), direction_, canvas_rotation_, script_,
         start_index_ + start, number_of_glyphs, number_of_characters);
 
-    unsigned sub_glyph_index = 0;
+    number_of_glyphs = 0;
     float total_advance = 0;
     ForEachGlyphInRange(
         0, start_index_ + start, start_index_ + end, 0,
         [&](const HarfBuzzRunGlyphData& glyph_data, float, uint16_t) -> bool {
-          HarfBuzzRunGlyphData& sub_glyph = run->glyph_data_[sub_glyph_index++];
+          auto& sub_glyph = run->glyph_data_[number_of_glyphs++];
           sub_glyph.glyph = glyph_data.glyph;
           sub_glyph.character_index = glyph_data.character_index - start;
           sub_glyph.safe_to_break_before = glyph_data.safe_to_break_before;
@@ -151,6 +137,11 @@
           return true;
         });
 
+    // Shrink glyph_data_ as the number of glyphs in the sub run is likely
+    // smaller than in the original.
+    DCHECK(run->glyph_data_.size() >= number_of_glyphs);
+    run->glyph_data_.resize(number_of_glyphs);
+
     run->width_ = total_advance;
     run->num_characters_ = number_of_characters;
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper.cc b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper.cc
index 70f2a59..83deea6 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper.cc
@@ -12,7 +12,7 @@
 void ChunkToLayerMapper::SwitchToChunk(const PaintChunk& chunk) {
   outset_for_raster_effects_ = chunk.outset_for_raster_effects;
 
-  const auto& new_chunk_state = chunk.properties.GetPropertyTreeState();
+  const auto& new_chunk_state = chunk.properties;
   if (new_chunk_state == chunk_state_)
     return;
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
index 9670719..79c1bf3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
@@ -27,27 +27,29 @@
   // A state containing arbitrary values which should not affect test results
   // if the state is used as a layer state.
   PropertyTreeState LayerState() {
-    DEFINE_STATIC_REF(
-        TransformPaintPropertyNode, transform,
-        CreateTransform(TransformPaintPropertyNode::Root(),
-                        TransformationMatrix().Translate(123, 456),
-                        FloatPoint3D(1, 2, 3)));
-    DEFINE_STATIC_REF(ClipPaintPropertyNode, clip,
-                      CreateClip(ClipPaintPropertyNode::Root(), transform,
-                                 FloatRoundedRect(12, 34, 56, 78)));
-    DEFINE_STATIC_REF(
-        EffectPaintPropertyNode, effect,
-        EffectPaintPropertyNode::Create(
-            EffectPaintPropertyNode::Root(),
-            EffectPaintPropertyNode::State{
-                transform, clip, kColorFilterLuminanceToAlpha,
-                CompositorFilterOperations(), 0.789f, SkBlendMode::kSrcIn}));
-    return PropertyTreeState(transform, clip, effect);
+    if (!layer_transform_) {
+      layer_transform_ =
+          CreateTransform(t0(), TransformationMatrix().Translate(123, 456),
+                          FloatPoint3D(1, 2, 3));
+      layer_clip_ = CreateClip(c0(), layer_transform_.get(),
+                               FloatRoundedRect(12, 34, 56, 78));
+      layer_effect_ = EffectPaintPropertyNode::Create(
+          e0(), EffectPaintPropertyNode::State{
+                    layer_transform_.get(), layer_clip_.get(),
+                    kColorFilterLuminanceToAlpha, CompositorFilterOperations(),
+                    0.789f, SkBlendMode::kSrcIn});
+    }
+    return PropertyTreeState(layer_transform_.get(), layer_clip_.get(),
+                             layer_effect_.get());
   }
 
   bool HasFilterThatMovesPixels(const ChunkToLayerMapper& mapper) {
     return mapper.has_filter_that_moves_pixels_;
   }
+
+  std::unique_ptr<TransformPaintPropertyNode> layer_transform_;
+  std::unique_ptr<ClipPaintPropertyNode> layer_clip_;
+  std::unique_ptr<EffectPaintPropertyNode> layer_effect_;
 };
 
 TEST_F(ChunkToLayerMapperTest, OneChunkUsingLayerState) {
@@ -92,9 +94,9 @@
 
 TEST_F(ChunkToLayerMapperTest, TwoChunkSameState) {
   ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
-  auto transform = CreateTransform(LayerState().Transform(),
+  auto transform = CreateTransform(*LayerState().Transform(),
                                    TransformationMatrix().Scale(2));
-  auto clip = CreateClip(LayerState().Clip(), LayerState().Transform(),
+  auto clip = CreateClip(*LayerState().Clip(), LayerState().Transform(),
                          FloatRoundedRect(10, 10, 100, 100));
   auto* effect = LayerState().Effect();
   auto chunk1 = Chunk(PropertyTreeState(transform.get(), clip.get(), effect));
@@ -123,16 +125,16 @@
 
 TEST_F(ChunkToLayerMapperTest, TwoChunkDifferentState) {
   ChunkToLayerMapper mapper(LayerState(), gfx::Vector2dF(10, 20));
-  auto transform1 = CreateTransform(LayerState().Transform(),
+  auto transform1 = CreateTransform(*LayerState().Transform(),
                                     TransformationMatrix().Scale(2));
-  auto clip1 = CreateClip(LayerState().Clip(), LayerState().Transform(),
+  auto clip1 = CreateClip(*LayerState().Clip(), LayerState().Transform(),
                           FloatRoundedRect(10, 10, 100, 100));
   auto* effect = LayerState().Effect();
   auto chunk1 = Chunk(PropertyTreeState(transform1.get(), clip1.get(), effect));
 
   auto transform2 =
-      CreateTransform(transform1, TransformationMatrix().Translate(20, 30));
-  auto clip2 = CreateClip(LayerState().Clip(), transform2,
+      CreateTransform(*transform1, TransformationMatrix().Translate(20, 30));
+  auto clip2 = CreateClip(*LayerState().Clip(), transform2.get(),
                           FloatRoundedRect(0, 0, 20, 20));
   auto chunk2 = Chunk(PropertyTreeState(transform2.get(), clip2.get(), effect));
 
@@ -165,13 +167,13 @@
   // Chunk2 has a blur filter. Should use the slow path.
   CompositorFilterOperations filter2;
   filter2.AppendBlurFilter(20);
-  auto effect2 = CreateFilterEffect(LayerState().Effect(), std::move(filter2));
+  auto effect2 = CreateFilterEffect(*LayerState().Effect(), std::move(filter2));
   auto chunk2 = Chunk(PropertyTreeState(LayerState().Transform(),
                                         LayerState().Clip(), effect2.get()));
 
   // Chunk3 has a different effect which inherits from chunk2's effect.
   // Should use the slow path.
-  auto effect3 = CreateOpacityEffect(effect2, 1.f);
+  auto effect3 = CreateOpacityEffect(*effect2, 1.f);
   auto chunk3 = Chunk(PropertyTreeState(LayerState().Transform(),
                                         LayerState().Clip(), effect3.get()));
 
@@ -179,7 +181,7 @@
   // Should use the fast path.
   CompositorFilterOperations filter4;
   filter4.AppendOpacityFilter(0.5);
-  auto effect4 = CreateFilterEffect(LayerState().Effect(), std::move(filter4));
+  auto effect4 = CreateFilterEffect(*LayerState().Effect(), std::move(filter4));
   auto chunk4 = Chunk(PropertyTreeState(LayerState().Transform(),
                                         LayerState().Clip(), effect4.get()));
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.cc b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.cc
index f6df3bc11..b84d8ad 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.cc
@@ -56,7 +56,7 @@
 
 PaintInvalidationReason
 CompositedLayerRasterInvalidator::ChunkPropertiesChanged(
-    const RefCountedPropertyTreeState& new_chunk_state,
+    const PropertyTreeState& new_chunk_state,
     const PaintChunkInfo& new_chunk,
     const PaintChunkInfo& old_chunk,
     const PropertyTreeState& layer_state) const {
diff --git a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.h b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.h
index cdf1daf..02529e4 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator.h
@@ -114,7 +114,7 @@
                                            PaintInvalidationReason,
                                            const String* debug_name = nullptr);
   PaintInvalidationReason ChunkPropertiesChanged(
-      const RefCountedPropertyTreeState& new_chunk_state,
+      const PropertyTreeState& new_chunk_state,
       const PaintChunkInfo& new_chunk,
       const PaintChunkInfo& old_chunk,
       const PropertyTreeState& layer_state) const;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator_test.cc b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator_test.cc
index c12c25e..adb969c 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/composited_layer_raster_invalidator_test.cc
@@ -39,19 +39,16 @@
   }
 
   CompositedLayerRasterInvalidatorTest& Properties(
-      const TransformPaintPropertyNode* t,
-      const ClipPaintPropertyNode* c = ClipPaintPropertyNode::Root(),
-      const EffectPaintPropertyNode* e = EffectPaintPropertyNode::Root()) {
-    auto& state = data_.chunks.back().properties;
-    state.SetTransform(t);
-    state.SetClip(c);
-    state.SetEffect(e);
+      const PropertyTreeState& state) {
+    data_.chunks.back().properties = state;
     return *this;
   }
 
   CompositedLayerRasterInvalidatorTest& Properties(
-      const RefCountedPropertyTreeState& state) {
-    data_.chunks.back().properties = state;
+      const TransformPaintPropertyNode& t,
+      const ClipPaintPropertyNode& c,
+      const EffectPaintPropertyNode& e) {
+    Properties(PropertyTreeState(&t, &c, &e));
     return *this;
   }
 
@@ -351,22 +348,17 @@
   FloatRoundedRect::Radii radii(FloatSize(1, 2), FloatSize(2, 3),
                                 FloatSize(3, 4), FloatSize(4, 5));
   FloatRoundedRect clip_rect(FloatRect(-1000, -1000, 2000, 2000), radii);
-  scoped_refptr<ClipPaintPropertyNode> clip0 =
-      CreateClip(ClipPaintPropertyNode::Root(),
-                 TransformPaintPropertyNode::Root(), clip_rect);
-  scoped_refptr<ClipPaintPropertyNode> clip2 =
-      CreateClip(clip0, TransformPaintPropertyNode::Root(), clip_rect);
+  auto clip0 = CreateClip(c0(), &t0(), clip_rect);
+  auto clip2 = CreateClip(*clip0, &t0(), clip_rect);
 
-  PropertyTreeState layer_state(TransformPaintPropertyNode::Root(), clip0.get(),
-                                EffectPaintPropertyNode::Root());
-  auto artifact =
-      Chunk(0)
-          .Properties(layer_state)
-          .Chunk(1)
-          .Properties(layer_state)
-          .Chunk(2)
-          .Properties(TransformPaintPropertyNode::Root(), clip2.get())
-          .Build();
+  PropertyTreeState layer_state(&t0(), clip0.get(), &e0());
+  auto artifact = Chunk(0)
+                      .Properties(layer_state)
+                      .Chunk(1)
+                      .Properties(layer_state)
+                      .Chunk(2)
+                      .Properties(t0(), *clip2, e0())
+                      .Build();
 
   GeometryMapperClipCache::ClearCache();
   invalidator.SetTracksRasterInvalidations(true);
@@ -382,10 +374,10 @@
                           .Properties(artifact.PaintChunks()[2].properties)
                           .Build();
   FloatRoundedRect new_clip_rect(FloatRect(-2000, -2000, 4000, 4000), radii);
-  clip0->Update(clip0->Parent(),
+  clip0->Update(*clip0->Parent(),
                 ClipPaintPropertyNode::State{clip0->LocalTransformSpace(),
                                              new_clip_rect});
-  clip2->Update(clip2->Parent(),
+  clip2->Update(*clip2->Parent(),
                 ClipPaintPropertyNode::State{clip2->LocalTransformSpace(),
                                              new_clip_rect});
 
@@ -423,21 +415,17 @@
 TEST_F(CompositedLayerRasterInvalidatorTest, ClipPropertyChangeSimple) {
   CompositedLayerRasterInvalidator invalidator(kNoopRasterInvalidation);
   FloatRoundedRect clip_rect(-1000, -1000, 2000, 2000);
-  scoped_refptr<ClipPaintPropertyNode> clip0 =
-      CreateClip(ClipPaintPropertyNode::Root(),
-                 TransformPaintPropertyNode::Root(), clip_rect);
-  scoped_refptr<ClipPaintPropertyNode> clip1 =
-      CreateClip(clip0, TransformPaintPropertyNode::Root(), clip_rect);
+  auto clip0 = CreateClip(c0(), &t0(), clip_rect);
+  auto clip1 = CreateClip(*clip0, &t0(), clip_rect);
 
   PropertyTreeState layer_state = PropertyTreeState::Root();
-  auto artifact =
-      Chunk(0)
-          .Properties(TransformPaintPropertyNode::Root(), clip0.get())
-          .Bounds(clip_rect.Rect())
-          .Chunk(1)
-          .Properties(TransformPaintPropertyNode::Root(), clip1.get())
-          .Bounds(clip_rect.Rect())
-          .Build();
+  auto artifact = Chunk(0)
+                      .Properties(t0(), *clip0, e0())
+                      .Bounds(clip_rect.Rect())
+                      .Chunk(1)
+                      .Properties(t0(), *clip1, e0())
+                      .Bounds(clip_rect.Rect())
+                      .Build();
 
   GeometryMapperClipCache::ClearCache();
   invalidator.SetTracksRasterInvalidations(true);
@@ -447,7 +435,7 @@
   // Change clip1 to bigger, which is still bound by clip0, resulting no actual
   // visual change.
   FloatRoundedRect new_clip_rect1(-2000, -2000, 4000, 4000);
-  clip1->Update(clip1->Parent(),
+  clip1->Update(*clip1->Parent(),
                 ClipPaintPropertyNode::State{clip1->LocalTransformSpace(),
                                              new_clip_rect1});
   auto new_artifact1 = Chunk(0)
@@ -465,7 +453,7 @@
 
   // Change clip1 to smaller.
   FloatRoundedRect new_clip_rect2(-500, -500, 1000, 1000);
-  clip1->Update(clip1->Parent(),
+  clip1->Update(*clip1->Parent(),
                 ClipPaintPropertyNode::State{clip1->LocalTransformSpace(),
                                              new_clip_rect2});
   auto new_artifact2 = Chunk(0)
@@ -498,7 +486,7 @@
 
   // Change clip1 bigger at one side.
   FloatRoundedRect new_clip_rect3(-500, -500, 2000, 1000);
-  clip1->Update(clip1->Parent(),
+  clip1->Update(*clip1->Parent(),
                 ClipPaintPropertyNode::State{clip1->LocalTransformSpace(),
                                              new_clip_rect3});
   auto new_artifact3 = Chunk(0)
@@ -525,20 +513,17 @@
 TEST_F(CompositedLayerRasterInvalidatorTest, TransformPropertyChange) {
   CompositedLayerRasterInvalidator invalidator(kNoopRasterInvalidation);
 
-  auto layer_transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                         TransformationMatrix().Scale(5));
-  auto transform0 = CreateTransform(layer_transform,
+  auto layer_transform = CreateTransform(t0(), TransformationMatrix().Scale(5));
+  auto transform0 = CreateTransform(*layer_transform,
                                     TransformationMatrix().Translate(10, 20));
   auto transform1 =
-      CreateTransform(transform0, TransformationMatrix().Translate(-50, -60));
+      CreateTransform(*transform0, TransformationMatrix().Translate(-50, -60));
 
-  PropertyTreeState layer_state(layer_transform.get(),
-                                ClipPaintPropertyNode::Root(),
-                                EffectPaintPropertyNode::Root());
+  PropertyTreeState layer_state(layer_transform.get(), &c0(), &e0());
   auto artifact = Chunk(0)
-                      .Properties(transform0.get())
+                      .Properties(*transform0, c0(), e0())
                       .Chunk(1)
-                      .Properties(transform1.get())
+                      .Properties(*transform1, c0(), e0())
                       .Build();
 
   GeometryMapperTransformCache::ClearCache();
@@ -548,7 +533,7 @@
 
   // Change layer_transform should not cause raster invalidation in the layer.
   layer_transform->Update(
-      layer_transform->Parent(),
+      *layer_transform->Parent(),
       TransformPaintPropertyNode::State{TransformationMatrix().Scale(10)});
   auto new_artifact = Chunk(0)
                           .Properties(artifact.PaintChunks()[0].properties)
@@ -565,11 +550,9 @@
   // raster invalidation in the layer. This simulates a composited layer is
   // scrolled from its original location.
   auto new_layer_transform = CreateTransform(
-      layer_transform, TransformationMatrix().Translate(-100, -200));
-  layer_state = PropertyTreeState(new_layer_transform.get(),
-                                  ClipPaintPropertyNode::Root(),
-                                  EffectPaintPropertyNode::Root());
-  transform0->Update(new_layer_transform,
+      *layer_transform, TransformationMatrix().Translate(-100, -200));
+  layer_state = PropertyTreeState(new_layer_transform.get(), &c0(), &e0());
+  transform0->Update(*new_layer_transform,
                      TransformPaintPropertyNode::State{transform0->Matrix()});
   auto new_artifact1 = Chunk(0)
                            .Properties(artifact.PaintChunks()[0].properties)
@@ -584,7 +567,7 @@
   // Removing transform nodes above the layer state should not cause raster
   // invalidation in the layer.
   layer_state = DefaultPropertyTreeState();
-  transform0->Update(layer_state.Transform(),
+  transform0->Update(*layer_state.Transform(),
                      TransformPaintPropertyNode::State{transform0->Matrix()});
   auto new_artifact2 = Chunk(0)
                            .Properties(artifact.PaintChunks()[0].properties)
@@ -600,11 +583,11 @@
   // and transform1 unchanged for chunk 2. We should invalidate only chunk 0
   // for changed paint property.
   transform0->Update(
-      layer_state.Transform(),
+      *layer_state.Transform(),
       TransformPaintPropertyNode::State{
           TransformationMatrix(transform0->Matrix()).Translate(20, 30)});
   transform1->Update(
-      transform0,
+      *transform0,
       TransformPaintPropertyNode::State{
           TransformationMatrix(transform1->Matrix()).Translate(-20, -30)});
   auto new_artifact3 = Chunk(0)
@@ -631,15 +614,12 @@
 TEST_F(CompositedLayerRasterInvalidatorTest, TransformPropertyTinyChange) {
   CompositedLayerRasterInvalidator invalidator(kNoopRasterInvalidation);
 
-  auto layer_transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                         TransformationMatrix().Scale(5));
+  auto layer_transform = CreateTransform(t0(), TransformationMatrix().Scale(5));
   auto chunk_transform = CreateTransform(
-      layer_transform, TransformationMatrix().Translate(10, 20));
+      *layer_transform, TransformationMatrix().Translate(10, 20));
 
-  PropertyTreeState layer_state(layer_transform.get(),
-                                ClipPaintPropertyNode::Root(),
-                                EffectPaintPropertyNode::Root());
-  auto artifact = Chunk(0).Properties(chunk_transform.get()).Build();
+  PropertyTreeState layer_state(layer_transform.get(), &c0(), &e0());
+  auto artifact = Chunk(0).Properties(*chunk_transform, c0(), e0()).Build();
 
   GeometryMapperTransformCache::ClearCache();
   invalidator.SetTracksRasterInvalidations(true);
@@ -647,13 +627,13 @@
   EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
 
   // Change chunk_transform by tiny difference, which should be ignored.
-  chunk_transform->Update(layer_state.Transform(),
+  chunk_transform->Update(*layer_state.Transform(),
                           TransformPaintPropertyNode::State{
                               TransformationMatrix(chunk_transform->Matrix())
                                   .Translate(0.0000001, -0.0000001)
                                   .Scale(1.0000001)
                                   .Rotate(0.0000001)});
-  auto new_artifact = Chunk(0).Properties(chunk_transform.get()).Build();
+  auto new_artifact = Chunk(0).Properties(*chunk_transform, c0(), e0()).Build();
 
   GeometryMapperTransformCache::ClearCache();
   invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
@@ -663,13 +643,14 @@
   // accumulation is large enough.
   bool invalidated = false;
   for (int i = 0; i < 100 && !invalidated; i++) {
-    chunk_transform->Update(layer_state.Transform(),
+    chunk_transform->Update(*layer_state.Transform(),
                             TransformPaintPropertyNode::State{
                                 TransformationMatrix(chunk_transform->Matrix())
                                     .Translate(0.0000001, -0.0000001)
                                     .Scale(1.0000001)
                                     .Rotate(0.0000001)});
-    auto new_artifact = Chunk(0).Properties(chunk_transform.get()).Build();
+    auto new_artifact =
+        Chunk(0).Properties(*chunk_transform, c0(), e0()).Build();
 
     GeometryMapperTransformCache::ClearCache();
     invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 6756dda..edf0e16 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -285,7 +285,7 @@
     : bounds(first_paint_chunk.bounds),
       rect_known_to_be_opaque(
           first_paint_chunk.known_to_be_opaque ? bounds : FloatRect()),
-      property_tree_state(first_paint_chunk.properties.GetPropertyTreeState()),
+      property_tree_state(first_paint_chunk.properties),
       requires_own_layer(chunk_requires_own_layer) {
   paint_chunk_indices.push_back(chunk_index);
 }
@@ -397,16 +397,12 @@
 
 bool PaintArtifactCompositor::MightOverlap(const PendingLayer& layer_a,
                                            const PendingLayer& layer_b) {
-  PropertyTreeState root_property_tree_state(TransformPaintPropertyNode::Root(),
-                                             ClipPaintPropertyNode::Root(),
-                                             EffectPaintPropertyNode::Root());
-
   FloatClipRect bounds_a(layer_a.bounds);
-  GeometryMapper::LocalToAncestorVisualRect(layer_a.property_tree_state,
-                                            root_property_tree_state, bounds_a);
+  GeometryMapper::LocalToAncestorVisualRect(
+      layer_a.property_tree_state, PropertyTreeState::Root(), bounds_a);
   FloatClipRect bounds_b(layer_b.bounds);
-  GeometryMapper::LocalToAncestorVisualRect(layer_b.property_tree_state,
-                                            root_property_tree_state, bounds_b);
+  GeometryMapper::LocalToAncestorVisualRect(
+      layer_b.property_tree_state, PropertyTreeState::Root(), bounds_b);
 
   return bounds_a.Rect().Intersects(bounds_b.Rect());
 }
@@ -575,8 +571,8 @@
     Vector<PendingLayer>& pending_layers) {
   Vector<PaintChunk>::const_iterator cursor =
       paint_artifact.PaintChunks().begin();
-  LayerizeGroup(paint_artifact, pending_layers,
-                *EffectPaintPropertyNode::Root(), cursor);
+  LayerizeGroup(paint_artifact, pending_layers, EffectPaintPropertyNode::Root(),
+                cursor);
   DCHECK_EQ(paint_artifact.PaintChunks().end(), cursor);
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index f215b49..56df523 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -197,9 +197,7 @@
   }
 
   void AddSimpleRectChunk(TestPaintArtifact& artifact) {
-    artifact
-        .Chunk(TransformPaintPropertyNode::Root(),
-               ClipPaintPropertyNode::Root(), EffectPaintPropertyNode::Root())
+    artifact.Chunk(t0(), c0(), e0())
         .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
   }
 
@@ -210,9 +208,7 @@
     if (include_preceding_chunk)
       AddSimpleRectChunk(artifact);
     auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), opacity);
-    artifact
-        .Chunk(TransformPaintPropertyNode::Root(),
-               ClipPaintPropertyNode::Root(), effect)
+    artifact.Chunk(t0(), c0(), *effect)
         .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
     if (include_subsequent_chunk)
       AddSimpleRectChunk(artifact);
@@ -237,17 +233,6 @@
 
 const auto kNotScrollingOnMain = MainThreadScrollingReason::kNotScrollingOnMain;
 
-// Convenient shorthands.
-const TransformPaintPropertyNode* t0() {
-  return TransformPaintPropertyNode::Root();
-}
-const ClipPaintPropertyNode* c0() {
-  return ClipPaintPropertyNode::Root();
-}
-const EffectPaintPropertyNode* e0() {
-  return EffectPaintPropertyNode::Root();
-}
-
 TEST_F(PaintArtifactCompositorTest, EmptyPaintArtifact) {
   PaintArtifact empty_artifact;
   Update(empty_artifact);
@@ -256,7 +241,7 @@
 
 TEST_F(PaintArtifactCompositorTest, OneChunkWithAnOffset) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, -50, 100, 100), Color::kWhite);
   Update(artifact.Build());
 
@@ -271,22 +256,16 @@
 
 TEST_F(PaintArtifactCompositorTest, OneTransform) {
   // A 90 degree clockwise rotation about (100, 100).
-  auto transform = CreateTransform(
-      TransformPaintPropertyNode::Root(), TransformationMatrix().Rotate(90),
-      FloatPoint3D(100, 100, 0), CompositingReason::k3DTransform);
+  auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(90),
+                                   FloatPoint3D(100, 100, 0),
+                                   CompositingReason::k3DTransform);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
-  artifact
-      .Chunk(transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -317,21 +296,17 @@
 
 TEST_F(PaintArtifactCompositorTest, TransformCombining) {
   // A translation by (5, 5) within a 2x scale about (10, 10).
-  auto transform1 = CreateTransform(
-      TransformPaintPropertyNode::Root(), TransformationMatrix().Scale(2),
-      FloatPoint3D(10, 10, 0), CompositingReason::k3DTransform);
+  auto transform1 =
+      CreateTransform(t0(), TransformationMatrix().Scale(2),
+                      FloatPoint3D(10, 10, 0), CompositingReason::k3DTransform);
   auto transform2 =
-      CreateTransform(transform1, TransformationMatrix().Translate(5, 5),
+      CreateTransform(*transform1, TransformationMatrix().Translate(5, 5),
                       FloatPoint3D(), CompositingReason::k3DTransform);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(transform1, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform1, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
-  artifact
-      .Chunk(transform2, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform2, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kBlack);
   Update(artifact.Build());
 
@@ -363,29 +338,23 @@
   backface_hidden_state.backface_visibility =
       TransformPaintPropertyNode::BackfaceVisibility::kHidden;
   auto backface_hidden_transform = TransformPaintPropertyNode::Create(
-      TransformPaintPropertyNode::Root(), std::move(backface_hidden_state));
+      t0(), std::move(backface_hidden_state));
 
   auto backface_inherited_transform = TransformPaintPropertyNode::Create(
-      backface_hidden_transform, TransformPaintPropertyNode::State{});
+      *backface_hidden_transform, TransformPaintPropertyNode::State{});
 
   TransformPaintPropertyNode::State backface_visible_state;
   backface_visible_state.backface_visibility =
       TransformPaintPropertyNode::BackfaceVisibility::kVisible;
   auto backface_visible_transform = TransformPaintPropertyNode::Create(
-      backface_hidden_transform, std::move(backface_visible_state));
+      *backface_hidden_transform, std::move(backface_visible_state));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(backface_hidden_transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*backface_hidden_transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
-  artifact
-      .Chunk(backface_inherited_transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*backface_inherited_transform, c0(), e0())
       .RectDrawing(FloatRect(100, 100, 100, 100), Color::kBlack);
-  artifact
-      .Chunk(backface_visible_transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*backface_visible_transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kDarkGray);
   Update(artifact.Build());
 
@@ -408,20 +377,17 @@
     // transform node flattens the transform. This is because Blink's notion of
     // flattening determines whether content within the node's local transform
     // is flattened, while cc's notion applies in the parent's coordinate space.
-    auto transform1 = CreateTransform(TransformPaintPropertyNode::Root(),
-                                      TransformationMatrix());
+    auto transform1 = CreateTransform(t0(), TransformationMatrix());
     auto transform2 =
-        CreateTransform(transform1, TransformationMatrix().Rotate3d(0, 45, 0));
+        CreateTransform(*transform1, TransformationMatrix().Rotate3d(0, 45, 0));
     TransformPaintPropertyNode::State transform3_state;
     transform3_state.matrix = TransformationMatrix().Rotate3d(0, 45, 0);
     transform3_state.flattens_inherited_transform = transform_is_flattened;
     auto transform3 = TransformPaintPropertyNode::Create(
-        transform2, std::move(transform3_state));
+        *transform2, std::move(transform3_state));
 
     TestPaintArtifact artifact;
-    artifact
-        .Chunk(transform3, ClipPaintPropertyNode::Root(),
-               EffectPaintPropertyNode::Root())
+    artifact.Chunk(*transform3, c0(), e0())
         .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
     Update(artifact.Build());
 
@@ -456,41 +422,32 @@
 
 TEST_F(PaintArtifactCompositorTest, SortingContextID) {
   // Has no 3D rendering context.
-  auto transform1 = CreateTransform(TransformPaintPropertyNode::Root(),
-                                    TransformationMatrix());
+  auto transform1 = CreateTransform(t0(), TransformationMatrix());
   // Establishes a 3D rendering context.
   TransformPaintPropertyNode::State transform2_3_state;
   transform2_3_state.rendering_context_id = 1;
   transform2_3_state.direct_compositing_reasons =
       CompositingReason::k3DTransform;
   auto transform2 = TransformPaintPropertyNode::Create(
-      transform1, std::move(transform2_3_state));
+      *transform1, std::move(transform2_3_state));
   // Extends the 3D rendering context of transform2.
   auto transform3 = TransformPaintPropertyNode::Create(
-      transform2, std::move(transform2_3_state));
+      *transform2, std::move(transform2_3_state));
   // Establishes a 3D rendering context distinct from transform2.
   TransformPaintPropertyNode::State transform4_state;
   transform4_state.rendering_context_id = 2;
   transform4_state.direct_compositing_reasons = CompositingReason::k3DTransform;
   auto transform4 = TransformPaintPropertyNode::Create(
-      transform2, std::move(transform4_state));
+      *transform2, std::move(transform4_state));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(transform1, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform1, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kWhite);
-  artifact
-      .Chunk(transform2, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform2, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kLightGray);
-  artifact
-      .Chunk(transform3, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform3, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kDarkGray);
-  artifact
-      .Chunk(transform4, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform4, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 200), Color::kBlack);
   Update(artifact.Build());
 
@@ -540,14 +497,10 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, OneClip) {
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(100, 100, 300, 200));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(100, 100, 300, 200));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip, e0())
       .RectDrawing(FloatRect(220, 80, 300, 200), Color::kBlack);
   Update(artifact.Build());
 
@@ -568,30 +521,19 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, NestedClips) {
-  auto clip1 = CreateClip(ClipPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(100, 100, 700, 700),
+  auto clip1 = CreateClip(c0(), &t0(), FloatRoundedRect(100, 100, 700, 700),
                           CompositingReason::kOverflowScrollingTouch);
-  auto clip2 = CreateClip(clip1, TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(200, 200, 700, 700),
+  auto clip2 = CreateClip(*clip1, &t0(), FloatRoundedRect(200, 200, 700, 700),
                           CompositingReason::kOverflowScrollingTouch);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip1,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip1, e0())
       .RectDrawing(FloatRect(300, 350, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip2,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip2, e0())
       .RectDrawing(FloatRect(300, 350, 100, 100), Color::kLightGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip1,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip1, e0())
       .RectDrawing(FloatRect(300, 350, 100, 100), Color::kDarkGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip2,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip2, e0())
       .RectDrawing(FloatRect(300, 350, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -637,18 +579,14 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, DeeplyNestedClips) {
-  Vector<scoped_refptr<ClipPaintPropertyNode>> clips;
+  Vector<std::unique_ptr<ClipPaintPropertyNode>> clips;
   for (unsigned i = 1; i <= 10; i++) {
-    clips.push_back(CreateClip(
-        clips.IsEmpty() ? ClipPaintPropertyNode::Root() : clips.back(),
-        TransformPaintPropertyNode::Root(),
-        FloatRoundedRect(5 * i, 0, 100, 200 - 10 * i)));
+    clips.push_back(CreateClip(clips.IsEmpty() ? c0() : *clips.back(), &t0(),
+                               FloatRoundedRect(5 * i, 0, 100, 200 - 10 * i)));
   }
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clips.back(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clips.back(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 200), Color::kWhite);
   Update(artifact.Build());
 
@@ -674,22 +612,16 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, SiblingClips) {
-  auto common_clip = CreateClip(ClipPaintPropertyNode::Root(),
-                                TransformPaintPropertyNode::Root(),
-                                FloatRoundedRect(0, 0, 800, 600));
-  auto clip1 = CreateClip(common_clip, TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(0, 0, 400, 600));
-  auto clip2 = CreateClip(common_clip, TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(400, 0, 400, 600));
+  auto common_clip = CreateClip(c0(), &t0(), FloatRoundedRect(0, 0, 800, 600));
+  auto clip1 =
+      CreateClip(*common_clip, &t0(), FloatRoundedRect(0, 0, 400, 600));
+  auto clip2 =
+      CreateClip(*common_clip, &t0(), FloatRoundedRect(400, 0, 400, 600));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip1,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip1, e0())
       .RectDrawing(FloatRect(0, 0, 640, 480), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip2,
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip2, e0())
       .RectDrawing(FloatRect(0, 0, 640, 480), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(2u, ContentLayerCount());
@@ -731,15 +663,11 @@
   layer->SetBounds(gfx::Size(400, 300));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact.Chunk(PropertyTreeState::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .ForeignLayer(FloatPoint(50, 60), IntSize(400, 300), layer);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -754,29 +682,22 @@
 
 TEST_F(PaintArtifactCompositorTest, EffectTreeConversion) {
   EffectPaintPropertyNode::State effect1_state;
-  effect1_state.local_transform_space = TransformPaintPropertyNode::Root();
-  effect1_state.output_clip = ClipPaintPropertyNode::Root();
+  effect1_state.local_transform_space = &t0();
+  effect1_state.output_clip = &c0();
   effect1_state.opacity = 0.5;
   effect1_state.direct_compositing_reasons = CompositingReason::kAll;
   effect1_state.compositor_element_id = CompositorElementId(2);
-  auto effect1 = EffectPaintPropertyNode::Create(
-      EffectPaintPropertyNode::Root(), std::move(effect1_state));
-  auto effect2 = CreateOpacityEffect(effect1, 0.3, CompositingReason::kAll);
-  auto effect3 = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.2,
-                                     CompositingReason::kAll);
+  auto effect1 =
+      EffectPaintPropertyNode::Create(e0(), std::move(effect1_state));
+  auto effect2 = CreateOpacityEffect(*effect1, 0.3, CompositingReason::kAll);
+  auto effect3 = CreateOpacityEffect(e0(), 0.2, CompositingReason::kAll);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect2.get())
+  artifact.Chunk(t0(), c0(), *effect2)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect1.get())
+  artifact.Chunk(t0(), c0(), *effect1)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect3.get())
+  artifact.Chunk(t0(), c0(), *effect3)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
   Update(artifact.Build());
 
@@ -784,7 +705,7 @@
 
   const cc::EffectTree& effect_tree = GetPropertyTrees().effect_tree;
   // Node #0 reserved for null; #1 for root render surface; #2 for
-  // EffectPaintPropertyNode::root(), plus 3 nodes for those created by
+  // e0(), plus 3 nodes for those created by
   // this test.
   ASSERT_EQ(5u, effect_tree.size());
 
@@ -829,8 +750,8 @@
   return state;
 }
 
-static scoped_refptr<ScrollPaintPropertyNode> CreateScroll(
-    scoped_refptr<const ScrollPaintPropertyNode> parent,
+static std::unique_ptr<ScrollPaintPropertyNode> CreateScroll(
+    const ScrollPaintPropertyNode& parent,
     const ScrollPaintPropertyNode::State& state_arg,
     MainThreadScrollingReasons main_thread_scrolling_reasons =
         MainThreadScrollingReason::kNotScrollingOnMain,
@@ -860,17 +781,11 @@
   CompositorElementId scroll_element_id = ScrollElementId(2);
   auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                              kNotScrollingOnMain, scroll_element_id);
-  auto scroll_translation =
-      CreateScrollTranslation(TransformPaintPropertyNode::Root(), 7, 9, scroll);
+  auto scroll_translation = CreateScrollTranslation(t0(), 7, 9, *scroll);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation);
-  artifact
-      .Chunk(scroll_translation, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0()).ScrollHitTest(*scroll_translation);
+  artifact.Chunk(*scroll_translation, c0(), e0())
       .RectDrawing(FloatRect(-110, 12, 170, 19), Color::kWhite);
   Update(artifact.Build());
 
@@ -920,20 +835,16 @@
 
 TEST_F(PaintArtifactCompositorTest, TransformUnderScrollNode) {
   auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1());
-  auto scroll_translation =
-      CreateScrollTranslation(TransformPaintPropertyNode::Root(), 7, 9, scroll);
+  auto scroll_translation = CreateScrollTranslation(t0(), 7, 9, *scroll);
 
   auto transform =
-      CreateTransform(scroll_translation, TransformationMatrix(),
+      CreateTransform(*scroll_translation, TransformationMatrix(),
                       FloatPoint3D(), CompositingReason::k3DTransform);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(scroll_translation, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*scroll_translation, c0(), e0())
       .RectDrawing(FloatRect(-20, 4, 60, 8), Color::kBlack)
-      .Chunk(transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+      .Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(1, -30, 5, 70), Color::kWhite);
   Update(artifact.Build());
 
@@ -969,7 +880,7 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, NestedScrollNodes) {
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto effect = CreateOpacityEffect(e0(), 0.5);
 
   CompositorElementId scroll_element_id_a = ScrollElementId(2);
   auto scroll_a = CreateScroll(
@@ -977,27 +888,22 @@
       MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
       scroll_element_id_a);
   auto scroll_translation_a = CreateScrollTranslation(
-      TransformPaintPropertyNode::Root(), 11, 13, scroll_a,
-      CompositingReason::kLayerForScrollingContents);
+      t0(), 11, 13, *scroll_a, CompositingReason::kLayerForScrollingContents);
 
   CompositorElementId scroll_element_id_b = ScrollElementId(3);
-  auto scroll_b = CreateScroll(scroll_a, ScrollState2(), kNotScrollingOnMain,
+  auto scroll_b = CreateScroll(*scroll_a, ScrollState2(), kNotScrollingOnMain,
                                scroll_element_id_b);
   auto scroll_translation_b =
-      CreateScrollTranslation(scroll_translation_a, 37, 41, scroll_b);
+      CreateScrollTranslation(*scroll_translation_a, 37, 41, *scroll_b);
   TestPaintArtifact artifact;
-  artifact.Chunk(scroll_translation_a, ClipPaintPropertyNode::Root(), effect)
+  artifact.Chunk(*scroll_translation_a, c0(), *effect)
       .RectDrawing(FloatRect(7, 11, 13, 17), Color::kWhite);
-  artifact
-      .Chunk(scroll_translation_a->Parent(), ClipPaintPropertyNode::Root(),
-             effect)
-      .ScrollHitTest(scroll_translation_a);
-  artifact.Chunk(scroll_translation_b, ClipPaintPropertyNode::Root(), effect)
+  artifact.Chunk(*scroll_translation_a->Parent(), c0(), *effect)
+      .ScrollHitTest(*scroll_translation_a);
+  artifact.Chunk(*scroll_translation_b, c0(), *effect)
       .RectDrawing(FloatRect(1, 2, 3, 5), Color::kWhite);
-  artifact
-      .Chunk(scroll_translation_b->Parent(), ClipPaintPropertyNode::Root(),
-             effect)
-      .ScrollHitTest(scroll_translation_b);
+  artifact.Chunk(*scroll_translation_b->Parent(), c0(), *effect)
+      .ScrollHitTest(*scroll_translation_b);
   Update(artifact.Build());
 
   const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
@@ -1028,29 +934,24 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, ScrollHitTestLayerOrder) {
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(0, 0, 100, 100));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(0, 0, 100, 100));
 
   CompositorElementId scroll_element_id = ScrollElementId(2);
   auto scroll = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                              kNotScrollingOnMain, scroll_element_id);
-  auto scroll_translation =
-      CreateScrollTranslation(TransformPaintPropertyNode::Root(), 7, 9, scroll,
-                              CompositingReason::kWillChangeCompositingHint);
+  auto scroll_translation = CreateScrollTranslation(
+      t0(), 7, 9, *scroll, CompositingReason::kWillChangeCompositingHint);
 
   auto transform = CreateTransform(
-      scroll_translation, TransformationMatrix().Translate(5, 5),
+      *scroll_translation, TransformationMatrix().Translate(5, 5),
       FloatPoint3D(), CompositingReason::k3DTransform);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(scroll_translation, clip, EffectPaintPropertyNode::Root())
+  artifact.Chunk(*scroll_translation, *clip, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(scroll_translation->Parent(), clip,
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation);
-  artifact.Chunk(transform, clip, EffectPaintPropertyNode::Root())
+  artifact.Chunk(*scroll_translation->Parent(), *clip, e0())
+      .ScrollHitTest(*scroll_translation);
+  artifact.Chunk(*transform, *clip, e0())
       .RectDrawing(FloatRect(0, 0, 50, 50), Color::kBlack);
   Update(artifact.Build());
 
@@ -1072,35 +973,27 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, NestedScrollHitTestLayerOrder) {
-  auto clip_1 = CreateClip(ClipPaintPropertyNode::Root(),
-                           TransformPaintPropertyNode::Root(),
-                           FloatRoundedRect(0, 0, 100, 100));
+  auto clip_1 = CreateClip(c0(), &t0(), FloatRoundedRect(0, 0, 100, 100));
   CompositorElementId scroll_1_element_id = ScrollElementId(1);
   auto scroll_1 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                                kNotScrollingOnMain, scroll_1_element_id);
   auto scroll_translation_1 = CreateScrollTranslation(
-      TransformPaintPropertyNode::Root(), 7, 9, scroll_1,
-      CompositingReason::kWillChangeCompositingHint);
+      t0(), 7, 9, *scroll_1, CompositingReason::kWillChangeCompositingHint);
 
-  auto clip_2 =
-      CreateClip(clip_1, scroll_translation_1, FloatRoundedRect(0, 0, 50, 50));
+  auto clip_2 = CreateClip(*clip_1, scroll_translation_1.get(),
+                           FloatRoundedRect(0, 0, 50, 50));
   CompositorElementId scroll_2_element_id = ScrollElementId(2);
   auto scroll_2 = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState2(),
                                kNotScrollingOnMain, scroll_2_element_id);
   auto scroll_translation_2 = CreateScrollTranslation(
-      TransformPaintPropertyNode::Root(), 0, 0, scroll_2,
-      CompositingReason::kWillChangeCompositingHint);
+      t0(), 0, 0, *scroll_2, CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(scroll_translation_1->Parent(), clip_1->Parent(),
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation_1);
-  artifact
-      .Chunk(scroll_translation_2->Parent(), clip_2->Parent(),
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation_2);
-  artifact.Chunk(scroll_translation_2, clip_2, EffectPaintPropertyNode::Root())
+  artifact.Chunk(*scroll_translation_1->Parent(), *clip_1->Parent(), e0())
+      .ScrollHitTest(*scroll_translation_1);
+  artifact.Chunk(*scroll_translation_2->Parent(), *clip_2->Parent(), e0())
+      .ScrollHitTest(*scroll_translation_2);
+  artifact.Chunk(*scroll_translation_2, *clip_2, e0())
       .RectDrawing(FloatRect(0, 0, 50, 50), Color::kWhite);
   Update(artifact.Build());
 
@@ -1138,24 +1031,18 @@
   auto scroll_a = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                                kNotScrollingOnMain, scroll_element_id_a);
   auto scroll_translation_a = CreateScrollTranslation(
-      TransformPaintPropertyNode::Root(), 11, 13, scroll_a,
-      CompositingReason::kLayerForScrollingContents);
+      t0(), 11, 13, *scroll_a, CompositingReason::kLayerForScrollingContents);
 
   CompositorElementId scroll_element_id_b = ScrollElementId(3);
-  auto scroll_b = CreateScroll(scroll_a, ScrollState2(), kNotScrollingOnMain,
+  auto scroll_b = CreateScroll(*scroll_a, ScrollState2(), kNotScrollingOnMain,
                                scroll_element_id_b);
   auto scroll_translation_b =
-      CreateScrollTranslation(scroll_translation_a, 37, 41, scroll_b);
+      CreateScrollTranslation(*scroll_translation_a, 37, 41, *scroll_b);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation_b);
-  artifact
-      .Chunk(scroll_translation_b, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
-      .ScrollHitTest(scroll_translation_a);
+  artifact.Chunk(t0(), c0(), e0()).ScrollHitTest(*scroll_translation_b);
+  artifact.Chunk(*scroll_translation_b, c0(), e0())
+      .ScrollHitTest(*scroll_translation_a);
   Update(artifact.Build());
 
   const cc::ScrollTree& scroll_tree = GetPropertyTrees().scroll_tree;
@@ -1192,13 +1079,9 @@
 
 TEST_F(PaintArtifactCompositorTest, MergeSimpleChunks) {
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1220,22 +1103,14 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, MergeClip) {
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 20, 50, 60));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 20, 50, 60));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), *clip, e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1260,22 +1135,16 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, Merge2DTransform) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(50, 50),
-                                   FloatPoint3D(100, 100, 0));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(50, 50),
+                      FloatPoint3D(100, 100, 0));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1300,24 +1169,18 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, Merge2DTransformDirectAncestor) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix(), FloatPoint3D(),
+  auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
                                    CompositingReason::k3DTransform);
-
   auto transform2 =
-      CreateTransform(transform.get(), TransformationMatrix().Translate(50, 50),
+      CreateTransform(*transform, TransformationMatrix().Translate(50, 50),
                       FloatPoint3D(100, 100, 0));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
   // The second chunk can merge into the first because it has a descendant
   // state of the first's transform and no direct compositing reason.
-  test_artifact
-      .Chunk(transform2.get(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform2, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1339,22 +1202,15 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, MergeTransformOrigin) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Rotate(45),
+  auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(45),
                                    FloatPoint3D(100, 100, 0));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1379,20 +1235,14 @@
 
 TEST_F(PaintArtifactCompositorTest, MergeOpacity) {
   float opacity = 2.0 / 255.0;
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), opacity);
+  auto effect = CreateOpacityEffect(e0(), opacity);
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  test_artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1419,28 +1269,20 @@
 TEST_F(PaintArtifactCompositorTest, MergeNested) {
   // Tests merging of an opacity effect, inside of a clip, inside of a
   // transform.
-
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(50, 50),
-                                   FloatPoint3D(100, 100, 0));
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(), transform.get(),
-                         FloatRoundedRect(10, 20, 50, 60));
-
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(50, 50),
+                      FloatPoint3D(100, 100, 0));
+  auto clip =
+      CreateClip(c0(), transform.get(), FloatRoundedRect(10, 20, 50, 60));
   float opacity = 2.0 / 255.0;
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), transform,
-                                    clip, opacity);
+  auto effect = CreateOpacityEffect(e0(), transform.get(), clip.get(), opacity);
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact.Chunk(transform.get(), clip.get(), effect.get())
+  test_artifact.Chunk(*transform, *clip, *effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1468,30 +1310,21 @@
   // Tests merging of an element which has a clipapplied to it,
   // but has an ancestor transform of them. This can happen for fixed-
   // or absolute-position elements which escape scroll transforms.
-
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(20, 25),
-                                   FloatPoint3D(100, 100, 0));
-
-  auto transform2 =
-      CreateTransform(transform.get(), TransformationMatrix().Translate(20, 25),
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
                       FloatPoint3D(100, 100, 0));
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(), transform2.get(),
-                         FloatRoundedRect(10, 20, 50, 60));
+  auto transform2 =
+      CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
+                      FloatPoint3D(100, 100, 0));
+  auto clip =
+      CreateClip(c0(), transform2.get(), FloatRoundedRect(10, 20, 50, 60));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), *clip, e0())
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1523,30 +1356,23 @@
   // but has an ancestor transform of them. This can happen for fixed-
   // or absolute-position elements which escape scroll transforms.
 
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(20, 25),
-                                   FloatPoint3D(100, 100, 0));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
+                      FloatPoint3D(100, 100, 0));
 
   auto transform2 =
-      CreateTransform(transform.get(), TransformationMatrix().Translate(20, 25),
+      CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
                       FloatPoint3D(100, 100, 0));
 
   float opacity = 2.0 / 255.0;
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), transform2,
-                                    ClipPaintPropertyNode::Root(), opacity);
+  auto effect = CreateOpacityEffect(e0(), transform2.get(), &c0(), opacity);
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  test_artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1576,33 +1402,25 @@
   // Tests merging of an element which has an effect applied to it,
   // but has an ancestor transform of them. This can happen for fixed-
   // or absolute-position elements which escape scroll transforms.
-
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(20, 25),
-                                   FloatPoint3D(100, 100, 0));
-
-  auto transform2 =
-      CreateTransform(transform.get(), TransformationMatrix().Translate(20, 25),
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
                       FloatPoint3D(100, 100, 0));
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(), transform.get(),
-                         FloatRoundedRect(10, 20, 50, 60));
+  auto transform2 =
+      CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
+                      FloatPoint3D(100, 100, 0));
+  auto clip =
+      CreateClip(c0(), transform.get(), FloatRoundedRect(10, 20, 50, 60));
 
   float opacity = 2.0 / 255.0;
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), transform2,
-                                    clip, opacity);
+  auto effect =
+      CreateOpacityEffect(e0(), transform2.get(), clip.get(), opacity);
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip.get(), effect.get())
+  test_artifact.Chunk(t0(), *clip, *effect)
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1630,27 +1448,16 @@
 TEST_F(PaintArtifactCompositorTest, ClipAndEffectNoTransform) {
   // Tests merging of an element which has a clip and effect in the root
   // transform space.
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 20, 50, 60));
-
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 20, 50, 60));
   float opacity = 2.0 / 255.0;
-  auto effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(), clip, opacity);
+  auto effect = CreateOpacityEffect(e0(), &t0(), clip.get(), opacity);
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip.get(), effect.get())
+  test_artifact.Chunk(t0(), *clip, *effect)
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1676,26 +1483,15 @@
 TEST_F(PaintArtifactCompositorTest, TwoClips) {
   // Tests merging of an element which has two clips in the root
   // transform space.
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(20, 30, 10, 20));
-
-  auto clip2 = CreateClip(clip.get(), TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(10, 20, 50, 60));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(20, 30, 10, 20));
+  auto clip2 = CreateClip(*clip, &t0(), FloatRoundedRect(10, 20, 50, 60));
 
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip2.get(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), *clip2, e0())
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1719,26 +1515,19 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, TwoTransformsClipBetween) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(20, 25),
-                                   FloatPoint3D(100, 100, 0));
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(0, 0, 50, 60));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
+                      FloatPoint3D(100, 100, 0));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(0, 0, 50, 60));
   auto transform2 =
-      CreateTransform(transform.get(), TransformationMatrix().Translate(20, 25),
+      CreateTransform(*transform, TransformationMatrix().Translate(20, 25),
                       FloatPoint3D(100, 100, 0));
   TestPaintArtifact test_artifact;
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(transform2.get(), clip.get(), EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform2, *clip, e0())
       .RectDrawing(FloatRect(0, 0, 300, 400), Color::kBlack);
-  test_artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1760,19 +1549,16 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, OverlapTransform) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(50, 50),
-                                   FloatPoint3D(100, 100, 0),
-                                   CompositingReason::k3DTransform);
+  auto transform = CreateTransform(
+      t0(), TransformationMatrix().Translate(50, 50), FloatPoint3D(100, 100, 0),
+      CompositingReason::k3DTransform);
 
   TestPaintArtifact test_artifact;
-  test_artifact.Chunk(PropertyTreeState::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  test_artifact
-      .Chunk(transform.get(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  test_artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  test_artifact.Chunk(PropertyTreeState::Root())
+  test_artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 200, 300), Color::kGray);
 
   const PaintArtifact& artifact = test_artifact.Build();
@@ -1797,18 +1583,17 @@
     EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
   }
 
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(99, 0),
-                                   FloatPoint3D(100, 100, 0));
+  auto transform = CreateTransform(
+      t0(), TransformationMatrix().Translate(99, 0), FloatPoint3D(100, 100, 0));
   {
     paint_chunk2.properties.SetTransform(transform.get());
     PendingLayer pending_layer2(paint_chunk2, 1, false);
     EXPECT_TRUE(MightOverlap(pending_layer, pending_layer2));
   }
 
-  auto transform2 = CreateTransform(TransformPaintPropertyNode::Root(),
-                                    TransformationMatrix().Translate(100, 0),
-                                    FloatPoint3D(100, 100, 0));
+  auto transform2 =
+      CreateTransform(t0(), TransformationMatrix().Translate(100, 0),
+                      FloatPoint3D(100, 100, 0));
   {
     paint_chunk2.properties.SetTransform(transform2.get());
     PendingLayer pending_layer2(paint_chunk2, 1, false);
@@ -1851,14 +1636,12 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, PendingLayerWithGeometry) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix().Translate(20, 25),
-                                   FloatPoint3D(100, 100, 0));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(20, 25),
+                      FloatPoint3D(100, 100, 0));
 
   PaintChunk chunk1 = DefaultChunk();
-  chunk1.properties = PropertyTreeState(TransformPaintPropertyNode::Root(),
-                                        ClipPaintPropertyNode::Root(),
-                                        EffectPaintPropertyNode::Root());
+  chunk1.properties = PropertyTreeState::Root();
   chunk1.bounds = FloatRect(0, 0, 30, 40);
 
   PendingLayer pending_layer(chunk1, 0, false);
@@ -1867,7 +1650,7 @@
 
   PaintChunk chunk2 = DefaultChunk();
   chunk2.properties = chunk1.properties;
-  chunk2.properties.SetTransform(transform);
+  chunk2.properties.SetTransform(transform.get());
   chunk2.bounds = FloatRect(0, 0, 50, 60);
   pending_layer.Merge(PendingLayer(chunk2, 1, false));
 
@@ -1878,9 +1661,7 @@
 // The test is disabled because opaque rect mapping is not implemented yet.
 TEST_F(PaintArtifactCompositorTest, PendingLayerKnownOpaque_DISABLED) {
   PaintChunk chunk1 = DefaultChunk();
-  chunk1.properties = PropertyTreeState(TransformPaintPropertyNode::Root(),
-                                        ClipPaintPropertyNode::Root(),
-                                        EffectPaintPropertyNode::Root());
+  chunk1.properties = PropertyTreeState::Root();
   chunk1.bounds = FloatRect(0, 0, 30, 40);
   chunk1.known_to_be_opaque = false;
   PendingLayer pending_layer(chunk1, 0, false);
@@ -1908,34 +1689,30 @@
   EXPECT_EQ(pending_layer.bounds, pending_layer.rect_known_to_be_opaque);
 }
 
-scoped_refptr<EffectPaintPropertyNode> CreateSampleEffectNodeWithElementId() {
+std::unique_ptr<EffectPaintPropertyNode> CreateSampleEffectNodeWithElementId() {
   EffectPaintPropertyNode::State state;
-  state.local_transform_space = TransformPaintPropertyNode::Root();
-  state.output_clip = ClipPaintPropertyNode::Root();
+  state.local_transform_space = &t0();
+  state.output_clip = &c0();
   state.opacity = 2.0 / 255.0;
   state.direct_compositing_reasons = CompositingReason::kActiveOpacityAnimation;
   state.compositor_element_id = CompositorElementId(2);
-  return EffectPaintPropertyNode::Create(EffectPaintPropertyNode::Root(),
-                                         std::move(state));
+  return EffectPaintPropertyNode::Create(e0(), std::move(state));
 }
 
-scoped_refptr<TransformPaintPropertyNode>
+std::unique_ptr<TransformPaintPropertyNode>
 CreateSampleTransformNodeWithElementId() {
   TransformPaintPropertyNode::State state;
   state.matrix.Rotate(90);
   state.origin = FloatPoint3D(100, 100, 0);
   state.direct_compositing_reasons = CompositingReason::k3DTransform;
   state.compositor_element_id = CompositorElementId(3);
-  return TransformPaintPropertyNode::Create(TransformPaintPropertyNode::Root(),
-                                            std::move(state));
+  return TransformPaintPropertyNode::Create(t0(), std::move(state));
 }
 
 TEST_F(PaintArtifactCompositorTest, TransformWithElementId) {
   auto transform = CreateSampleTransformNodeWithElementId();
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -1946,9 +1723,7 @@
 TEST_F(PaintArtifactCompositorTest, EffectWithElementId) {
   auto effect = CreateSampleEffectNodeWithElementId();
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -1956,27 +1731,22 @@
 }
 
 TEST_F(PaintArtifactCompositorTest, CompositedLuminanceMask) {
-  auto masked =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 1.0,
-                          CompositingReason::kIsolateCompositedDescendants);
+  auto masked = CreateOpacityEffect(
+      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
-  masking_state.local_transform_space = TransformPaintPropertyNode::Root();
-  masking_state.output_clip = ClipPaintPropertyNode::Root();
+  masking_state.local_transform_space = &t0();
+  masking_state.output_clip = &c0();
   masking_state.color_filter = kColorFilterLuminanceToAlpha;
   masking_state.blend_mode = SkBlendMode::kDstIn;
   masking_state.direct_compositing_reasons =
       CompositingReason::kSquashingDisallowed;
   auto masking =
-      EffectPaintPropertyNode::Create(masked, std::move(masking_state));
+      EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             masked.get())
+  artifact.Chunk(t0(), c0(), *masked)
       .RectDrawing(FloatRect(100, 100, 200, 200), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             masking.get())
+  artifact.Chunk(t0(), c0(), *masking)
       .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
   Update(artifact.Build());
   ASSERT_EQ(2u, ContentLayerCount());
@@ -2007,22 +1777,16 @@
 
 TEST_F(PaintArtifactCompositorTest, UpdateProducesNewSequenceNumber) {
   // A 90 degree clockwise rotation about (100, 100).
-  auto transform = CreateTransform(
-      TransformPaintPropertyNode::Root(), TransformationMatrix().Rotate(90),
-      FloatPoint3D(100, 100, 0), CompositingReason::k3DTransform);
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(100, 100, 300, 200));
-
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto transform = CreateTransform(t0(), TransformationMatrix().Rotate(90),
+                                   FloatPoint3D(100, 100, 0),
+                                   CompositingReason::k3DTransform);
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(100, 100, 300, 200));
+  auto effect = CreateOpacityEffect(e0(), 0.5);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(transform, clip, effect)
+  artifact.Chunk(*transform, *clip, *effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kWhite);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kGray);
   Update(artifact.Build());
 
@@ -2057,19 +1821,12 @@
 TEST_F(PaintArtifactCompositorTest, DecompositeClip) {
   // A clipped paint chunk that gets merged into a previous layer should
   // only contribute clipped bounds to the layer bound.
-
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(75, 75, 100, 100));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(75, 75, 100, 100));
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), clip.get(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), *clip, e0())
       .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -2084,20 +1841,14 @@
   // group compositing descendants should not be composited and can merge
   // with other chunks.
 
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto effect = CreateOpacityEffect(e0(), 0.5);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -2110,21 +1861,14 @@
 
 TEST_F(PaintArtifactCompositorTest, DirectlyCompositedEffect) {
   // An effect node with direct compositing shall be composited.
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5f,
-                                    CompositingReason::kAll);
+  auto effect = CreateOpacityEffect(e0(), 0.5f, CompositingReason::kAll);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(3u, ContentLayerCount());
@@ -2152,22 +1896,16 @@
   // A paint chunk may enter multiple level effects with or without compositing
   // reasons. This test verifies we still decomposite effects without a direct
   // reason, but stop at a directly composited effect.
-  auto effect1 = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.1f);
-  auto effect2 = CreateOpacityEffect(effect1, 0.2f, CompositingReason::kAll);
-  auto effect3 = CreateOpacityEffect(effect2, 0.3f);
+  auto effect1 = CreateOpacityEffect(e0(), 0.1f);
+  auto effect2 = CreateOpacityEffect(*effect1, 0.2f, CompositingReason::kAll);
+  auto effect3 = CreateOpacityEffect(*effect2, 0.3f);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect3.get())
+  artifact.Chunk(t0(), c0(), *effect3)
       .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(3u, ContentLayerCount());
@@ -2197,21 +1935,16 @@
 TEST_F(PaintArtifactCompositorTest, IndirectlyCompositedEffect) {
   // An effect node without direct compositing still needs to be composited
   // for grouping, if some chunks need to be composited.
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5f);
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix(), FloatPoint3D(),
+  auto effect = CreateOpacityEffect(e0(), 0.5f);
+  auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
                                    CompositingReason::k3DTransform);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 25, 100, 100), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(25, 75, 100, 100), Color::kGray);
-  artifact.Chunk(transform.get(), ClipPaintPropertyNode::Root(), effect.get())
+  artifact.Chunk(*transform, c0(), *effect)
       .RectDrawing(FloatRect(75, 75, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(3u, ContentLayerCount());
@@ -2238,34 +1971,25 @@
 TEST_F(PaintArtifactCompositorTest, DecompositedEffectNotMergingDueToOverlap) {
   // This tests an effect that doesn't need to be composited, but needs
   // separate backing due to overlap with a previous composited effect.
-  auto effect1 = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.1f);
-  auto effect2 = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.2f);
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix(), FloatPoint3D(),
+  auto effect1 = CreateOpacityEffect(e0(), 0.1f);
+  auto effect2 = CreateOpacityEffect(e0(), 0.2f);
+  auto transform = CreateTransform(t0(), TransformationMatrix(), FloatPoint3D(),
                                    CompositingReason::k3DTransform);
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(0, 0, 50, 50), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect1.get())
+  artifact.Chunk(t0(), c0(), *effect1)
       .RectDrawing(FloatRect(100, 0, 50, 50), Color::kGray);
   // This chunk has a transform that must be composited, thus causing effect1
   // to be composited too.
-  artifact.Chunk(transform.get(), ClipPaintPropertyNode::Root(), effect1.get())
+  artifact.Chunk(*transform, c0(), *effect1)
       .RectDrawing(FloatRect(200, 0, 50, 50), Color::kGray);
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect2.get())
+  artifact.Chunk(t0(), c0(), *effect2)
       .RectDrawing(FloatRect(200, 100, 50, 50), Color::kGray);
   // This chunk overlaps with the 2nd chunk, but is seemingly safe to merge.
   // However because effect1 gets composited due to a composited transform,
   // we can't merge with effect1 nor skip it to merge with the first chunk.
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect2.get())
+  artifact.Chunk(t0(), c0(), *effect2)
       .RectDrawing(FloatRect(100, 0, 50, 50), Color::kGray);
 
   Update(artifact.Build());
@@ -2299,12 +2023,9 @@
   auto transform = CreateSampleTransformNodeWithElementId();
   auto effect = CreateSampleEffectNodeWithElementId();
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(transform, ClipPaintPropertyNode::Root(),
-             EffectPaintPropertyNode::Root())
+  artifact.Chunk(*transform, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack)
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+      .Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
 
   CompositorElementIdSet composited_element_ids;
@@ -2411,12 +2132,9 @@
 
 TEST_F(PaintArtifactCompositorTest,
        DontSkipChunkWithTinyOpacityAndDirectCompositingReason) {
-  auto effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.0001f,
-                                    CompositingReason::kCanvas);
+  auto effect = CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kCanvas);
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect)
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -2424,14 +2142,12 @@
 
 TEST_F(PaintArtifactCompositorTest,
        SkipChunkWithTinyOpacityAndVisibleChildEffectNode) {
-  auto tiny_effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(),
-                                         0.0001f, CompositingReason::kNone);
+  auto tiny_effect =
+      CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kNone);
   auto visible_effect =
-      CreateOpacityEffect(tiny_effect, 0.5f, CompositingReason::kNone);
+      CreateOpacityEffect(*tiny_effect, 0.5f, CompositingReason::kNone);
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             visible_effect)
+  artifact.Chunk(t0(), c0(), *visible_effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(0u, ContentLayerCount());
@@ -2440,13 +2156,11 @@
 TEST_F(
     PaintArtifactCompositorTest,
     DontSkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingParent) {
-  auto tiny_effect = CreateOpacityEffect(EffectPaintPropertyNode::Root(),
-                                         0.0001f, CompositingReason::kCanvas);
-  auto visible_effect = CreateOpacityEffect(tiny_effect, 0.5f);
+  auto tiny_effect =
+      CreateOpacityEffect(e0(), 0.0001f, CompositingReason::kCanvas);
+  auto visible_effect = CreateOpacityEffect(*tiny_effect, 0.5f);
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             visible_effect)
+  artifact.Chunk(t0(), c0(), *visible_effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -2454,14 +2168,11 @@
 
 TEST_F(PaintArtifactCompositorTest,
        SkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingChild) {
-  auto tiny_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.0001f);
+  auto tiny_effect = CreateOpacityEffect(e0(), 0.0001f);
   auto visible_effect =
-      CreateOpacityEffect(tiny_effect, 0.5f, CompositingReason::kCanvas);
+      CreateOpacityEffect(*tiny_effect, 0.5f, CompositingReason::kCanvas);
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             visible_effect)
+  artifact.Chunk(t0(), c0(), *visible_effect)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(0u, ContentLayerCount());
@@ -2473,9 +2184,7 @@
 
   {
     TestPaintArtifact artifact;
-    artifact
-        .Chunk(transform, ClipPaintPropertyNode::Root(),
-               EffectPaintPropertyNode::Root())
+    artifact.Chunk(*transform, c0(), e0())
         .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
 
     Update(artifact.Build());
@@ -2498,11 +2207,11 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2547,12 +2256,12 @@
        SynthesizedClipIndirectlyCompositedClipPath) {
   // This tests the case that a clip node needs to be synthesized due to
   // applying clip path to a composited effect.
-  auto c1 = CreateClipPathClip(c0(), t0(), FloatRoundedRect(50, 50, 300, 200));
-  auto e1 = CreateOpacityEffect(e0(), t0(), c1, 1,
+  auto c1 = CreateClipPathClip(c0(), &t0(), FloatRoundedRect(50, 50, 300, 200));
+  auto e1 = CreateOpacityEffect(e0(), &t0(), c1.get(), 1,
                                 CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e1)
+  artifact.Chunk(t0(), *c1, *e1)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2607,13 +2316,13 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t1, c1, e0())
+  artifact.Chunk(*t1, *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2676,15 +2385,15 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t1, c0(), e0())
+  artifact.Chunk(*t1, c0(), e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2765,18 +2474,17 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
-
-  auto e1 = CreateOpacityEffect(e0(), t0(), c1, 1,
+  auto e1 = CreateOpacityEffect(e0(), &t0(), c1.get(), 1,
                                 CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e1)
+  artifact.Chunk(t0(), *c1, *e1)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2836,7 +2544,7 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
 
   CompositorFilterOperations non_trivial_filter;
@@ -2845,11 +2553,11 @@
                                CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e1)
+  artifact.Chunk(t0(), *c1, *e1)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -2941,23 +2649,23 @@
   FloatSize corner(5, 5);
   FloatRoundedRect rrect(FloatRect(50, 50, 300, 200), corner, corner, corner,
                          corner);
-  auto c1 = CreateClip(c0(), t0(), rrect,
+  auto c1 = CreateClip(c0(), &t0(), rrect,
                        CompositingReason::kWillChangeCompositingHint);
 
   EffectPaintPropertyNode::State e1_state;
-  e1_state.local_transform_space = t0();
-  e1_state.output_clip = c1;
+  e1_state.local_transform_space = &t0();
+  e1_state.output_clip = c1.get();
   e1_state.blend_mode = SkBlendMode::kMultiply;
   e1_state.direct_compositing_reasons =
       CompositingReason::kWillChangeCompositingHint;
   auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(e1_state));
 
   TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e1)
+  artifact.Chunk(t0(), *c1, *e1)
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
-  artifact.Chunk(t0(), c1, e0())
+  artifact.Chunk(t0(), *c1, e0())
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -3045,9 +2753,7 @@
 TEST_F(PaintArtifactCompositorTest, WillBeRemovedFromFrame) {
   auto effect = CreateSampleEffectNodeWithElementId();
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect.get())
+  artifact.Chunk(t0(), c0(), *effect)
       .RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
   Update(artifact.Build());
 
@@ -3060,7 +2766,7 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsNonOpaque) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -3069,7 +2775,7 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsOpaque) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
       .KnownToBeOpaque();
   Update(artifact.Build());
@@ -3079,7 +2785,7 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsOpaqueSubpixel) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100.5, 100.5, 200, 200), Color::kBlack)
       .KnownToBeOpaque();
   Update(artifact.Build());
@@ -3090,10 +2796,10 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedNonOpaque) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
       .KnownToBeOpaque()
-      .Chunk(PropertyTreeState::Root())
+      .Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(200, 200, 200, 200), Color::kBlack)
       .KnownToBeOpaque();
   Update(artifact.Build());
@@ -3104,10 +2810,10 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque1) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 300, 300), Color::kBlack)
       .KnownToBeOpaque()
-      .Chunk(PropertyTreeState::Root())
+      .Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(200, 200, 200, 200), Color::kBlack)
       .KnownToBeOpaque();
   Update(artifact.Build());
@@ -3118,10 +2824,10 @@
 
 TEST_F(PaintArtifactCompositorTest, ContentsOpaqueUnitedOpaque2) {
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 200, 200), Color::kBlack)
       .KnownToBeOpaque()
-      .Chunk(PropertyTreeState::Root())
+      .Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(100, 100, 300, 300), Color::kBlack)
       .KnownToBeOpaque();
   Update(artifact.Build());
@@ -3135,18 +2841,13 @@
 TEST_F(PaintArtifactCompositorTest, DecompositeEffectWithNoOutputClip) {
   // This test verifies effect nodes with no output clip correctly decomposites
   // if there is no compositing reasons.
-  auto clip1 = CreateClip(ClipPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(75, 75, 100, 100));
-
-  auto effect1 =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(), nullptr, 0.5);
+  auto clip1 = CreateClip(c0(), &t0(), FloatRoundedRect(75, 75, 100, 100));
+  auto effect1 = CreateOpacityEffect(e0(), &t0(), nullptr, 0.5);
 
   TestPaintArtifact artifact;
-  artifact.Chunk(PropertyTreeState::Root())
+  artifact.Chunk(t0(), c0(), e0())
       .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
-  artifact.Chunk(TransformPaintPropertyNode::Root(), clip1.get(), effect1.get())
+  artifact.Chunk(t0(), *clip1, *effect1)
       .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -3160,20 +2861,15 @@
 TEST_F(PaintArtifactCompositorTest, CompositedEffectWithNoOutputClip) {
   // This test verifies effect nodes with no output clip but has compositing
   // reason correctly squash children chunks and assign clip node.
-  auto clip1 = CreateClip(ClipPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(75, 75, 100, 100));
+  auto clip1 = CreateClip(c0(), &t0(), FloatRoundedRect(75, 75, 100, 100));
 
-  auto effect1 = CreateOpacityEffect(EffectPaintPropertyNode::Root(),
-                                     TransformPaintPropertyNode::Root(),
-                                     nullptr, 0.5, CompositingReason::kAll);
+  auto effect1 =
+      CreateOpacityEffect(e0(), &t0(), nullptr, 0.5, CompositingReason::kAll);
 
   TestPaintArtifact artifact;
-  artifact
-      .Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-             effect1.get())
+  artifact.Chunk(t0(), c0(), *effect1)
       .RectDrawing(FloatRect(50, 50, 100, 100), Color::kGray);
-  artifact.Chunk(TransformPaintPropertyNode::Root(), clip1.get(), effect1.get())
+  artifact.Chunk(t0(), *clip1, *effect1)
       .RectDrawing(FloatRect(100, 100, 100, 100), Color::kGray);
   Update(artifact.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -3187,13 +2883,9 @@
 
 TEST_F(PaintArtifactCompositorTest, LayerRasterInvalidationWithClip) {
   // The layer's painting is initially not clipped.
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 20, 300, 400));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 20, 300, 400));
   TestPaintArtifact artifact1;
-  artifact1
-      .Chunk(TransformPaintPropertyNode::Root(), clip,
-             EffectPaintPropertyNode::Root())
+  artifact1.Chunk(t0(), *clip, e0())
       .RectDrawing(FloatRect(50, 50, 200, 200), Color::kBlack);
   Update(artifact1.Build());
   ASSERT_EQ(1u, ContentLayerCount());
@@ -3207,9 +2899,7 @@
 
   // The layer's painting overflows the left, top, right edges of the clip .
   TestPaintArtifact artifact2;
-  artifact2
-      .Chunk(artifact1.Client(0), TransformPaintPropertyNode::Root(), clip,
-             EffectPaintPropertyNode::Root())
+  artifact2.Chunk(artifact1.Client(0), t0(), *clip, e0())
       .RectDrawing(artifact1.Client(1), FloatRect(0, 0, 400, 200),
                    Color::kBlack);
   layer->ResetNeedsDisplayForTesting();
@@ -3227,9 +2917,7 @@
 
   // The layer's painting overflows all edges of the clip.
   TestPaintArtifact artifact3;
-  artifact3
-      .Chunk(artifact1.Client(0), TransformPaintPropertyNode::Root(), clip,
-             EffectPaintPropertyNode::Root())
+  artifact3.Chunk(artifact1.Client(0), t0(), *clip, e0())
       .RectDrawing(artifact1.Client(1), FloatRect(-100, -200, 500, 800),
                    Color::kBlack);
   layer->ResetNeedsDisplayForTesting();
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
index 29203926..10b7226 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
@@ -653,7 +653,7 @@
       // "draw" this record in order to ensure that the effect has correct
       // visual rects.
       if ((!record || record->size() == 0) &&
-          chunk_state.Effect() == EffectPaintPropertyNode::Root()) {
+          chunk_state.Effect() == &EffectPaintPropertyNode::Root()) {
         continue;
       }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
index 9034cb8..ddbe6519 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer_test.cc
@@ -147,17 +147,6 @@
     EXPECT_EQ(SkRRect(r), clip_op->rrect);                     \
   } while (false)
 
-// Convenient shorthands.
-const TransformPaintPropertyNode* t0() {
-  return TransformPaintPropertyNode::Root();
-}
-const ClipPaintPropertyNode* c0() {
-  return ClipPaintPropertyNode::Root();
-}
-const EffectPaintPropertyNode* e0() {
-  return EffectPaintPropertyNode::Root();
-}
-
 PaintChunk::Id DefaultId() {
   DEFINE_STATIC_LOCAL(FakeDisplayItemClient, fake_client,
                       ("FakeDisplayItemClient", LayoutRect(0, 0, 100, 100)));
@@ -169,9 +158,9 @@
   DisplayItemList items = DisplayItemList(0);
 
   // Add a paint chunk with a non-empty paint record and given property nodes.
-  void AddChunk(const TransformPaintPropertyNode* t,
-                const ClipPaintPropertyNode* c,
-                const EffectPaintPropertyNode* e,
+  void AddChunk(const TransformPaintPropertyNode& t,
+                const ClipPaintPropertyNode& c,
+                const EffectPaintPropertyNode& e,
                 const FloatRect& bounds = FloatRect(0, 0, 100, 100)) {
     auto record = sk_make_sp<PaintRecord>();
     record->push<cc::DrawRectOp>(bounds, cc::PaintFlags());
@@ -180,14 +169,14 @@
 
   // Add a paint chunk with a given paint record and property nodes.
   void AddChunk(sk_sp<PaintRecord> record,
-                const TransformPaintPropertyNode* t,
-                const ClipPaintPropertyNode* c,
-                const EffectPaintPropertyNode* e,
+                const TransformPaintPropertyNode& t,
+                const ClipPaintPropertyNode& c,
+                const EffectPaintPropertyNode& e,
                 const FloatRect& bounds = FloatRect(0, 0, 100, 100)) {
     size_t i = items.size();
     items.AllocateAndConstruct<DrawingDisplayItem>(
         DefaultId().client, DefaultId().type, std::move(record));
-    chunks.emplace_back(i, i + 1, DefaultId(), PropertyTreeState(t, c, e));
+    chunks.emplace_back(i, i + 1, DefaultId(), PropertyTreeState(&t, &c, &e));
     chunks.back().bounds = bounds;
   }
 };
@@ -196,12 +185,12 @@
   // This test verifies effects are applied as a group.
   auto e1 = CreateOpacityEffect(e0(), 0.5f);
   TestChunks chunks;
-  chunks.AddChunk(t0(), c0(), e1.get(), FloatRect(0, 0, 50, 50));
-  chunks.AddChunk(t0(), c0(), e1.get(), FloatRect(20, 20, 70, 70));
+  chunks.AddChunk(t0(), c0(), *e1, FloatRect(0, 0, 50, 50));
+  chunks.AddChunk(t0(), c0(), *e1, FloatRect(20, 20, 70, 70));
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(
@@ -216,15 +205,15 @@
 TEST_F(PaintChunksToCcLayerTest, EffectGroupingNested) {
   // This test verifies nested effects are grouped properly.
   auto e1 = CreateOpacityEffect(e0(), 0.5f);
-  auto e2 = CreateOpacityEffect(e1, 0.5f);
-  auto e3 = CreateOpacityEffect(e1, 0.5f);
+  auto e2 = CreateOpacityEffect(*e1, 0.5f);
+  auto e3 = CreateOpacityEffect(*e1, 0.5f);
   TestChunks chunks;
-  chunks.AddChunk(t0(), c0(), e2.get());
-  chunks.AddChunk(t0(), c0(), e3.get(), FloatRect(111, 222, 333, 444));
+  chunks.AddChunk(t0(), c0(), *e2);
+  chunks.AddChunk(t0(), c0(), *e3, FloatRect(111, 222, 333, 444));
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(
@@ -245,19 +234,19 @@
 TEST_F(PaintChunksToCcLayerTest, EffectFilterGroupingNestedWithTransforms) {
   // This test verifies nested effects with transforms are grouped properly.
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto t2 = CreateTransform(t1, TransformationMatrix().Translate(-50, -50));
-  auto e1 = CreateOpacityEffect(e0(), t2, c0(), 0.5);
+  auto t2 = CreateTransform(*t1, TransformationMatrix().Translate(-50, -50));
+  auto e1 = CreateOpacityEffect(e0(), t2.get(), &c0(), 0.5);
 
   CompositorFilterOperations filter;
   filter.AppendBlurFilter(5);
-  auto e2 = CreateFilterEffect(e1, filter, FloatPoint(60, 60));
+  auto e2 = CreateFilterEffect(*e1, filter, FloatPoint(60, 60));
   TestChunks chunks;
-  chunks.AddChunk(t2.get(), c0(), e1.get(), FloatRect(0, 0, 50, 50));
-  chunks.AddChunk(t1.get(), c0(), e2.get(), FloatRect(20, 20, 70, 70));
+  chunks.AddChunk(*t2, c0(), *e1, FloatRect(0, 0, 50, 50));
+  chunks.AddChunk(*t1, c0(), *e2, FloatRect(20, 20, 70, 70));
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(
@@ -295,22 +284,22 @@
   // ConversionContext.
   // Refer to PaintChunksToCcLayer.cpp for detailed explanation.
   // (Search "State management example".)
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c2 = CreateClip(c1, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c3 = CreateClip(c2, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c4 = CreateClip(c3, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto e1 = CreateOpacityEffect(e0(), t0(), c2, 0.5);
-  auto e2 = CreateOpacityEffect(e1, t0(), c4, 0.5);
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c2 = CreateClip(*c1, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c3 = CreateClip(*c2, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c4 = CreateClip(*c3, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto e1 = CreateOpacityEffect(e0(), &t0(), c2.get(), 0.5);
+  auto e2 = CreateOpacityEffect(*e1, &t0(), c4.get(), 0.5);
   TestChunks chunks;
-  chunks.AddChunk(t0(), c2.get(), e0());
-  chunks.AddChunk(t0(), c3.get(), e0());
-  chunks.AddChunk(t0(), c4.get(), e2.get(), FloatRect(0, 0, 50, 50));
-  chunks.AddChunk(t0(), c3.get(), e1.get(), FloatRect(20, 20, 70, 70));
-  chunks.AddChunk(t0(), c4.get(), e0());
+  chunks.AddChunk(t0(), *c2, e0());
+  chunks.AddChunk(t0(), *c3, e0());
+  chunks.AddChunk(t0(), *c4, *e2, FloatRect(0, 0, 50, 50));
+  chunks.AddChunk(t0(), *c3, *e1, FloatRect(20, 20, 70, 70));
+  chunks.AddChunk(t0(), *c4, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(*output, PaintRecordMatcher::Make(
@@ -349,13 +338,13 @@
   //     <div style="position:fixed;">Clipped but not scroll along.</div>
   // </div>
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto c1 = CreateClip(c0(), t1, FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), t1.get(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e0());
+  chunks.AddChunk(t0(), *c1, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(*output,
@@ -377,14 +366,14 @@
   //   </div>
   // </div>
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto e1 = CreateOpacityEffect(e0(), t1, c0(), 0.5);
+  auto e1 = CreateOpacityEffect(e0(), t1.get(), &c0(), 0.5);
   TestChunks chunks;
-  chunks.AddChunk(t0(), c0(), e1.get());
-  chunks.AddChunk(t1.get(), c0(), e1.get());
+  chunks.AddChunk(t0(), c0(), *e1);
+  chunks.AddChunk(*t1, c0(), *e1);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(*output,
@@ -413,13 +402,14 @@
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
   CompositorFilterOperations filter;
   filter.AppendBlurFilter(5);
-  auto e1 = CreateFilterEffect(e0(), t1, c0(), filter, FloatPoint(66, 88));
+  auto e1 =
+      CreateFilterEffect(e0(), t1.get(), &c0(), filter, FloatPoint(66, 88));
   TestChunks chunks;
-  chunks.AddChunk(t0(), c0(), e1.get());
+  chunks.AddChunk(t0(), c0(), *e1);
 
   auto output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(
@@ -446,10 +436,10 @@
   // This test verifies a layer with composited property state does not
   // apply properties again internally.
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   auto e1 = CreateOpacityEffect(e0(), 0.5f);
   TestChunks chunks;
-  chunks.AddChunk(t1.get(), c1.get(), e1.get());
+  chunks.AddChunk(*t1, *c1, *e1);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
@@ -464,10 +454,10 @@
   // This test verifies chunks that have a shallower transform state than the
   // layer can still be painted.
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   auto e1 = CreateOpacityEffect(e0(), 0.5f);
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e1.get());
+  chunks.AddChunk(t0(), *c1, *e1);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
@@ -484,16 +474,16 @@
 
 TEST_F(PaintChunksToCcLayerTest, EffectWithNoOutputClip) {
   // This test verifies effect with no output clip can be correctly processed.
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c2 = CreateClip(c1, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto e1 = CreateOpacityEffect(e0(), t0(), nullptr, 0.5);
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c2 = CreateClip(*c1, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto e1 = CreateOpacityEffect(e0(), &t0(), nullptr, 0.5);
 
   TestChunks chunks;
-  chunks.AddChunk(t0(), c2.get(), e1.get());
+  chunks.AddChunk(t0(), *c2, *e1);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c1.get(), e0()),
+          chunks.chunks, PropertyTreeState(&t0(), c1.get(), &e0()),
           gfx::Vector2dF(), chunks.items,
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
@@ -510,16 +500,16 @@
 
 TEST_F(PaintChunksToCcLayerTest,
        EffectWithNoOutputClipNestedInDecompositedEffect) {
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   auto e1 = CreateOpacityEffect(e0(), 0.5);
-  auto e2 = CreateOpacityEffect(e1, t0(), nullptr, 0.5);
+  auto e2 = CreateOpacityEffect(*e1, &t0(), nullptr, 0.5);
 
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e2.get());
+  chunks.AddChunk(t0(), *c1, *e2);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
   EXPECT_THAT(
@@ -538,16 +528,16 @@
 
 TEST_F(PaintChunksToCcLayerTest,
        EffectWithNoOutputClipNestedInCompositedEffect) {
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   auto e1 = CreateOpacityEffect(e0(), 0.5);
-  auto e2 = CreateOpacityEffect(e1, t0(), nullptr, 0.5);
+  auto e2 = CreateOpacityEffect(*e1, &t0(), nullptr, 0.5);
 
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e2.get());
+  chunks.AddChunk(t0(), *c1, *e2);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e1.get()),
+          chunks.chunks, PropertyTreeState(&t0(), &c0(), e1.get()),
           gfx::Vector2dF(), chunks.items,
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
@@ -564,16 +554,16 @@
 
 TEST_F(PaintChunksToCcLayerTest,
        EffectWithNoOutputClipNestedInCompositedEffectAndClip) {
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
   auto e1 = CreateOpacityEffect(e0(), 0.5);
-  auto e2 = CreateOpacityEffect(e1, t0(), nullptr, 0.5);
+  auto e2 = CreateOpacityEffect(*e1, &t0(), nullptr, 0.5);
 
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e2.get());
+  chunks.AddChunk(t0(), *c1, *e2);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c1.get(), e1.get()),
+          chunks.chunks, PropertyTreeState(&t0(), c1.get(), e1.get()),
           gfx::Vector2dF(), chunks.items,
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
@@ -589,15 +579,15 @@
   auto layer_transform =
       CreateTransform(t0(), TransformationMatrix().Scale(20));
   auto chunk_transform = CreateTransform(
-      layer_transform, TransformationMatrix().Translate(50, 100));
+      *layer_transform, TransformationMatrix().Translate(50, 100));
 
   TestChunks chunks;
-  chunks.AddChunk(chunk_transform.get(), c0(), e0());
+  chunks.AddChunk(*chunk_transform, c0(), e0());
 
   auto cc_list = base::MakeRefCounted<cc::DisplayItemList>(
       cc::DisplayItemList::kTopLevelDisplayItemList);
   PaintChunksToCcLayer::ConvertInto(
-      chunks.chunks, PropertyTreeState(layer_transform.get(), c0(), e0()),
+      chunks.chunks, PropertyTreeState(layer_transform.get(), &c0(), &e0()),
       gfx::Vector2dF(100, 200), FloatSize(), chunks.items, *cc_list);
   EXPECT_EQ(gfx::Rect(-50, -100, 100, 100), cc_list->VisualRectForTesting(4));
 
@@ -613,15 +603,15 @@
 }
 
 TEST_F(PaintChunksToCcLayerTest, NoncompositedClipPath) {
-  auto c1 = CreateClipPathClip(c0(), t0(), FloatRoundedRect(1, 2, 3, 4));
+  auto c1 = CreateClipPathClip(c0(), &t0(), FloatRoundedRect(1, 2, 3, 4));
   TestChunks chunks;
-  chunks.AddChunk(t0(), c1.get(), e0());
+  chunks.AddChunk(t0(), *c1, e0());
 
   auto cc_list = base::MakeRefCounted<cc::DisplayItemList>(
       cc::DisplayItemList::kTopLevelDisplayItemList);
-  PaintChunksToCcLayer::ConvertInto(
-      chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
-      FloatSize(), chunks.items, *cc_list);
+  PaintChunksToCcLayer::ConvertInto(chunks.chunks, PropertyTreeState::Root(),
+                                    gfx::Vector2dF(), FloatSize(), chunks.items,
+                                    *cc_list);
 
   EXPECT_THAT(
       *cc_list->ReleaseAsRecord(),
@@ -633,22 +623,22 @@
 }
 
 TEST_F(PaintChunksToCcLayerTest, EmptyClipsAreElided) {
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c1c2 = CreateClip(c1, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c2 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1c2 = CreateClip(*c1, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c2 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
 
   TestChunks chunks;
-  chunks.AddChunk(nullptr, t0(), c1.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1c2.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1c2.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1c2.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1.get(), e0());
+  chunks.AddChunk(nullptr, t0(), *c1, e0());
+  chunks.AddChunk(nullptr, t0(), *c1c2, e0());
+  chunks.AddChunk(nullptr, t0(), *c1c2, e0());
+  chunks.AddChunk(nullptr, t0(), *c1c2, e0());
+  chunks.AddChunk(nullptr, t0(), *c1, e0());
   // D1
-  chunks.AddChunk(t0(), c2.get(), e0());
+  chunks.AddChunk(t0(), *c2, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -662,23 +652,23 @@
 }
 
 TEST_F(PaintChunksToCcLayerTest, NonEmptyClipsAreStored) {
-  auto c1 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c1c2 = CreateClip(c1, t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
-  auto c2 = CreateClip(c0(), t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c1c2 = CreateClip(*c1, &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
+  auto c2 = CreateClip(c0(), &t0(), FloatRoundedRect(0.f, 0.f, 1.f, 1.f));
 
   TestChunks chunks;
-  chunks.AddChunk(nullptr, t0(), c1.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1c2.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1c2.get(), e0());
+  chunks.AddChunk(nullptr, t0(), *c1, e0());
+  chunks.AddChunk(nullptr, t0(), *c1c2, e0());
+  chunks.AddChunk(nullptr, t0(), *c1c2, e0());
   // D1
-  chunks.AddChunk(t0(), c1c2.get(), e0());
-  chunks.AddChunk(nullptr, t0(), c1.get(), e0());
+  chunks.AddChunk(t0(), *c1c2, e0());
+  chunks.AddChunk(nullptr, t0(), *c1, e0());
   // D2
-  chunks.AddChunk(t0(), c2.get(), e0());
+  chunks.AddChunk(t0(), *c2, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -698,11 +688,11 @@
 
   TestChunks chunks;
   chunks.AddChunk(nullptr, t0(), c0(), e0());
-  chunks.AddChunk(nullptr, t0(), c0(), e1.get());
+  chunks.AddChunk(nullptr, t0(), c0(), *e1);
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -716,20 +706,20 @@
 TEST_F(PaintChunksToCcLayerTest, CombineClips) {
   FloatRoundedRect clip_rect(0, 0, 100, 100);
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto c1 = CreateClip(c0(), t0(), clip_rect);
-  auto c2 = CreateClip(c1, t0(), clip_rect);
-  auto c3 = CreateClip(c2, t1, clip_rect);
-  auto c4 = CreateClip(c3, t1, clip_rect);
-  auto c5 = CreateClipPathClip(c4, t1, clip_rect);
-  auto c6 = CreateClip(c5, t1, clip_rect);
+  auto c1 = CreateClip(c0(), &t0(), clip_rect);
+  auto c2 = CreateClip(*c1, &t0(), clip_rect);
+  auto c3 = CreateClip(*c2, t1.get(), clip_rect);
+  auto c4 = CreateClip(*c3, t1.get(), clip_rect);
+  auto c5 = CreateClipPathClip(*c4, t1.get(), clip_rect);
+  auto c6 = CreateClip(*c5, t1.get(), clip_rect);
 
   TestChunks chunks;
-  chunks.AddChunk(t1.get(), c6.get(), e0());
-  chunks.AddChunk(t1.get(), c3.get(), e0());
+  chunks.AddChunk(*t1, *c6, e0());
+  chunks.AddChunk(*t1, *c3, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -758,20 +748,20 @@
   FloatRoundedRect small_rounded_clip_rect(FloatRect(0, 0, 100, 100), corner,
                                            corner, corner, corner);
 
-  auto c1 = CreateClip(c0(), t0(), clip_rect);
-  auto c2 = CreateClip(c1, t0(), small_rounded_clip_rect);
-  auto c3 = CreateClip(c2, t0(), clip_rect);
-  auto c4 = CreateClip(c3, t0(), big_rounded_clip_rect);
-  auto c5 = CreateClip(c4, t0(), clip_rect);
-  auto c6 = CreateClip(c5, t0(), big_rounded_clip_rect);
-  auto c7 = CreateClip(c6, t0(), small_rounded_clip_rect);
+  auto c1 = CreateClip(c0(), &t0(), clip_rect);
+  auto c2 = CreateClip(*c1, &t0(), small_rounded_clip_rect);
+  auto c3 = CreateClip(*c2, &t0(), clip_rect);
+  auto c4 = CreateClip(*c3, &t0(), big_rounded_clip_rect);
+  auto c5 = CreateClip(*c4, &t0(), clip_rect);
+  auto c6 = CreateClip(*c5, &t0(), big_rounded_clip_rect);
+  auto c7 = CreateClip(*c6, &t0(), small_rounded_clip_rect);
 
   TestChunks chunks;
-  chunks.AddChunk(t0(), c7.get(), e0());
+  chunks.AddChunk(t0(), *c7, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -799,21 +789,21 @@
 
 TEST_F(PaintChunksToCcLayerTest, ChunksSamePropertyTreeState) {
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2.f));
-  auto t2 = CreateTransform(t1, TransformationMatrix().Scale(3.f));
-  auto c1 = CreateClip(c0(), t1, FloatRoundedRect(0, 0, 100, 100));
+  auto t2 = CreateTransform(*t1, TransformationMatrix().Scale(3.f));
+  auto c1 = CreateClip(c0(), t1.get(), FloatRoundedRect(0, 0, 100, 100));
 
   TestChunks chunks;
   chunks.AddChunk(t0(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e0());
-  chunks.AddChunk(t1.get(), c1.get(), e0());
-  chunks.AddChunk(t1.get(), c1.get(), e0());
-  chunks.AddChunk(t2.get(), c1.get(), e0());
-  chunks.AddChunk(t2.get(), c1.get(), e0());
+  chunks.AddChunk(*t1, c0(), e0());
+  chunks.AddChunk(*t1, c0(), e0());
+  chunks.AddChunk(*t1, *c1, e0());
+  chunks.AddChunk(*t1, *c1, e0());
+  chunks.AddChunk(*t2, *c1, e0());
+  chunks.AddChunk(*t2, *c1, e0());
 
   sk_sp<PaintRecord> output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -836,23 +826,23 @@
 
 TEST_F(PaintChunksToCcLayerTest, NoOpForIdentityTransforms) {
   auto t1 = CreateTransform(t0(), TransformationMatrix());
-  auto t2 = CreateTransform(t1, TransformationMatrix());
-  auto t3 = CreateTransform(t2, TransformationMatrix());
-  auto c1 = CreateClip(c0(), t2, FloatRoundedRect(0, 0, 100, 100));
-  auto c2 = CreateClip(c1, t3, FloatRoundedRect(0, 0, 200, 50));
+  auto t2 = CreateTransform(*t1, TransformationMatrix());
+  auto t3 = CreateTransform(*t2, TransformationMatrix());
+  auto c1 = CreateClip(c0(), t2.get(), FloatRoundedRect(0, 0, 100, 100));
+  auto c2 = CreateClip(*c1, t3.get(), FloatRoundedRect(0, 0, 200, 50));
 
   TestChunks chunks;
   chunks.AddChunk(t0(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e0());
+  chunks.AddChunk(*t1, c0(), e0());
   chunks.AddChunk(t0(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e0());
-  chunks.AddChunk(t2.get(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e0());
-  chunks.AddChunk(t1.get(), c2.get(), e0());
+  chunks.AddChunk(*t1, c0(), e0());
+  chunks.AddChunk(*t2, c0(), e0());
+  chunks.AddChunk(*t1, c0(), e0());
+  chunks.AddChunk(*t1, *c2, e0());
 
   auto output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -871,17 +861,17 @@
 
 TEST_F(PaintChunksToCcLayerTest, EffectsWithSameTransform) {
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2));
-  auto e1 = CreateOpacityEffect(e0(), t1, c0(), 0.1f);
-  auto e2 = CreateOpacityEffect(e0(), t1, c0(), 0.2f);
+  auto e1 = CreateOpacityEffect(e0(), t1.get(), &c0(), 0.1f);
+  auto e2 = CreateOpacityEffect(e0(), t1.get(), &c0(), 0.2f);
 
   TestChunks chunks;
   chunks.AddChunk(t0(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e1.get());
-  chunks.AddChunk(t1.get(), c0(), e2.get());
+  chunks.AddChunk(*t1, c0(), *e1);
+  chunks.AddChunk(*t1, c0(), *e2);
 
   auto output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
@@ -900,17 +890,17 @@
 
 TEST_F(PaintChunksToCcLayerTest, NestedEffectsWithSameTransform) {
   auto t1 = CreateTransform(t0(), TransformationMatrix().Scale(2));
-  auto e1 = CreateOpacityEffect(e0(), t1, c0(), 0.1f);
-  auto e2 = CreateOpacityEffect(e1, t1, c0(), 0.2f);
+  auto e1 = CreateOpacityEffect(e0(), t1.get(), &c0(), 0.1f);
+  auto e2 = CreateOpacityEffect(*e1, t1.get(), &c0(), 0.2f);
 
   TestChunks chunks;
   chunks.AddChunk(t0(), c0(), e0());
-  chunks.AddChunk(t1.get(), c0(), e1.get());
-  chunks.AddChunk(t1.get(), c0(), e2.get());
+  chunks.AddChunk(*t1, c0(), *e1);
+  chunks.AddChunk(*t1, c0(), *e2);
 
   auto output =
       PaintChunksToCcLayer::Convert(
-          chunks.chunks, PropertyTreeState(t0(), c0(), e0()), gfx::Vector2dF(),
+          chunks.chunks, PropertyTreeState::Root(), gfx::Vector2dF(),
           chunks.items, cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
           ->ReleaseAsRecord();
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 403b74e..b414400 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -91,7 +91,7 @@
   transform_tree.SetFromScreen(kRealRootNodeId, from_screen);
   transform_tree.set_needs_update(true);
 
-  transform_node_map_.Set(TransformPaintPropertyNode::Root(),
+  transform_node_map_.Set(&TransformPaintPropertyNode::Root(),
                           transform_node.id);
   root_layer_->SetTransformTreeIndex(transform_node.id);
 }
@@ -109,7 +109,7 @@
       gfx::SizeF(root_layer_->layer_tree_host()->device_viewport_size()));
   clip_node.transform_id = kRealRootNodeId;
 
-  clip_node_map_.Set(ClipPaintPropertyNode::Root(), clip_node.id);
+  clip_node_map_.Set(&ClipPaintPropertyNode::Root(), clip_node.id);
   root_layer_->SetClipTreeIndex(clip_node.id);
 }
 
@@ -133,7 +133,7 @@
 
   current_effect_id_ = effect_node.id;
   current_effect_type_ = CcEffectType::kEffect;
-  current_effect_ = EffectPaintPropertyNode::Root();
+  current_effect_ = &EffectPaintPropertyNode::Root();
   current_clip_ = current_effect_->OutputClip();
 }
 
@@ -146,7 +146,7 @@
   DCHECK_EQ(scroll_node.id, kSecondaryRootNodeId);
   scroll_node.transform_id = kSecondaryRootNodeId;
 
-  scroll_node_map_.Set(ScrollPaintPropertyNode::Root(), scroll_node.id);
+  scroll_node_map_.Set(&ScrollPaintPropertyNode::Root(), scroll_node.id);
   root_layer_->SetScrollTreeIndex(scroll_node.id);
 }
 
@@ -314,8 +314,7 @@
   mask_effect.has_render_surface = true;
   mask_effect.blend_mode = SkBlendMode::kDstIn;
 
-  const TransformPaintPropertyNode* clip_space =
-      current_clip_->LocalTransformSpace();
+  const auto* clip_space = current_clip_->LocalTransformSpace();
   root_layer_->AddChild(mask_layer);
   mask_layer->set_property_tree_sequence_number(sequence_number_);
   mask_layer->SetTransformTreeIndex(EnsureCompositorTransformNode(clip_space));
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc b/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
index 2bb296b..e7ffa52 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer_test.cc
@@ -223,11 +223,11 @@
 
 TEST_P(GraphicsLayerTest, PaintRecursively) {
   IntRect interest_rect(1, 2, 3, 4);
-  auto* transform_root = TransformPaintPropertyNode::Root();
+  const auto& transform_root = TransformPaintPropertyNode::Root();
   auto transform1 =
       CreateTransform(transform_root, TransformationMatrix().Translate(10, 20));
   auto transform2 =
-      CreateTransform(transform1, TransformationMatrix().Scale(2));
+      CreateTransform(*transform1, TransformationMatrix().Scale(2));
 
   client_.SetPainter([&](const GraphicsLayer* layer, GraphicsContext& context,
                          GraphicsLayerPaintingPhase, const IntRect&) {
@@ -250,13 +250,13 @@
   transform1->Update(transform_root,
                      TransformPaintPropertyNode::State{
                          TransformationMatrix().Translate(20, 30)});
-  EXPECT_TRUE(transform1->Changed(*transform_root));
-  EXPECT_TRUE(transform2->Changed(*transform_root));
+  EXPECT_TRUE(transform1->Changed(transform_root));
+  EXPECT_TRUE(transform2->Changed(transform_root));
   client_.SetNeedsRepaint(true);
   graphics_layer_->PaintRecursively();
 
-  EXPECT_FALSE(transform1->Changed(*transform_root));
-  EXPECT_FALSE(transform2->Changed(*transform_root));
+  EXPECT_FALSE(transform1->Changed(transform_root));
+  EXPECT_FALSE(transform2->Changed(transform_root));
 }
 
 TEST_P(GraphicsLayerTest, SetDrawsContentFalse) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
index 2f4ae85..adf29b91 100644
--- a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.cc
@@ -8,12 +8,11 @@
 
 namespace blink {
 
-ClipPaintPropertyNode* ClipPaintPropertyNode::Root() {
-  DEFINE_STATIC_REF(
+const ClipPaintPropertyNode& ClipPaintPropertyNode::Root() {
+  DEFINE_STATIC_LOCAL(
       ClipPaintPropertyNode, root,
-      (ClipPaintPropertyNode::Create(
-          nullptr, State{TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(LayoutRect::InfiniteIntRect())})));
+      (nullptr, State{&TransformPaintPropertyNode::Root(),
+                      FloatRoundedRect(LayoutRect::InfiniteIntRect())}));
   return root;
 }
 
@@ -22,7 +21,7 @@
   if (Parent())
     json->SetString("parent", String::Format("%p", Parent()));
   json->SetString("localTransformSpace",
-                  String::Format("%p", state_.local_transform_space.get()));
+                  String::Format("%p", state_.local_transform_space));
   json->SetString("rect", state_.clip_rect.ToString());
   if (state_.clip_rect_excluding_overlay_scrollbars) {
     json->SetString("rectExcludingOverlayScrollbars",
diff --git a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
index 1702e55b..8cc614e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_CLIP_PAINT_PROPERTY_NODE_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_CLIP_PAINT_PROPERTY_NODE_H_
 
+#include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h"
@@ -29,7 +30,7 @@
   // To make it less verbose and more readable to construct and update a node,
   // a struct with default values is used to represent the state.
   struct State {
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space;
+    const TransformPaintPropertyNode* local_transform_space = nullptr;
     FloatRoundedRect clip_rect;
     base::Optional<FloatRoundedRect> clip_rect_excluding_overlay_scrollbars;
     scoped_refptr<const RefCountedPath> clip_path;
@@ -53,18 +54,17 @@
   };
 
   // This node is really a sentinel, and does not represent a real clip space.
-  static ClipPaintPropertyNode* Root();
+  static const ClipPaintPropertyNode& Root();
 
-  static scoped_refptr<ClipPaintPropertyNode> Create(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
+  static std::unique_ptr<ClipPaintPropertyNode> Create(
+      const ClipPaintPropertyNode& parent,
       State&& state) {
-    return base::AdoptRef(
-        new ClipPaintPropertyNode(std::move(parent), std::move(state)));
+    return base::WrapUnique(
+        new ClipPaintPropertyNode(&parent, std::move(state)));
   }
 
-  bool Update(scoped_refptr<const ClipPaintPropertyNode> parent,
-              State&& state) {
-    bool parent_changed = SetParent(parent);
+  bool Update(const ClipPaintPropertyNode& parent, State&& state) {
+    bool parent_changed = SetParent(&parent);
     if (state == state_)
       return parent_changed;
 
@@ -73,14 +73,13 @@
     return true;
   }
 
-  bool EqualIgnoringHitTestRects(
-      scoped_refptr<const ClipPaintPropertyNode> parent,
-      const State& state) const {
+  bool EqualIgnoringHitTestRects(const ClipPaintPropertyNode* parent,
+                                 const State& state) const {
     return parent == Parent() && state_.EqualIgnoringHitTestRects(state);
   }
 
   const TransformPaintPropertyNode* LocalTransformSpace() const {
-    return state_.local_transform_space.get();
+    return state_.local_transform_space;
   }
   const FloatRoundedRect& ClipRect() const { return state_.clip_rect; }
   const FloatRoundedRect& ClipRectExcludingOverlayScrollbars() const {
@@ -98,8 +97,8 @@
 #if DCHECK_IS_ON()
   // The clone function is used by FindPropertiesNeedingUpdate.h for recording
   // a clip node before it has been updated, to later detect changes.
-  scoped_refptr<ClipPaintPropertyNode> Clone() const {
-    return base::AdoptRef(new ClipPaintPropertyNode(Parent(), State(state_)));
+  std::unique_ptr<ClipPaintPropertyNode> Clone() const {
+    return base::WrapUnique(new ClipPaintPropertyNode(Parent(), State(state_)));
   }
 
   // The equality operator is used by FindPropertiesNeedingUpdate.h for checking
@@ -115,9 +114,8 @@
   size_t CacheMemoryUsageInBytes() const;
 
  private:
-  ClipPaintPropertyNode(scoped_refptr<const ClipPaintPropertyNode> parent,
-                        State&& state)
-      : PaintPropertyNode(std::move(parent)), state_(std::move(state)) {}
+  ClipPaintPropertyNode(const ClipPaintPropertyNode* parent, State&& state)
+      : PaintPropertyNode(parent), state_(std::move(state)) {}
 
   // For access to GetClipCache();
   friend class GeometryMapper;
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
index 70852c21..0750252 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
@@ -6,11 +6,10 @@
 
 namespace blink {
 
-EffectPaintPropertyNode* EffectPaintPropertyNode::Root() {
-  DEFINE_STATIC_REF(EffectPaintPropertyNode, root,
-                    (EffectPaintPropertyNode::Create(
-                        nullptr, State{TransformPaintPropertyNode::Root(),
-                                       ClipPaintPropertyNode::Root()})));
+const EffectPaintPropertyNode& EffectPaintPropertyNode::Root() {
+  DEFINE_STATIC_LOCAL(EffectPaintPropertyNode, root,
+                      (nullptr, State{&TransformPaintPropertyNode::Root(),
+                                      &ClipPaintPropertyNode::Root()}));
   return root;
 }
 
@@ -27,8 +26,8 @@
   if (Parent())
     json->SetString("parent", String::Format("%p", Parent()));
   json->SetString("localTransformSpace",
-                  String::Format("%p", state_.local_transform_space.get()));
-  json->SetString("outputClip", String::Format("%p", state_.output_clip.get()));
+                  String::Format("%p", state_.local_transform_space));
+  json->SetString("outputClip", String::Format("%p", state_.output_clip));
   if (state_.color_filter != kColorFilterNone)
     json->SetInteger("colorFilter", state_.color_filter);
   if (!state_.filter.IsEmpty())
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index fea3860..753e862 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -32,10 +32,10 @@
     //    and effects under the same parent.
     // 2. Some effects are spatial (namely blur filter and reflection), the
     //    effect parameters will be specified in the local space.
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space;
+    const TransformPaintPropertyNode* local_transform_space = nullptr;
     // The output of the effect can be optionally clipped when composited onto
     // the current backdrop.
-    scoped_refptr<const ClipPaintPropertyNode> output_clip;
+    const ClipPaintPropertyNode* output_clip = nullptr;
     // Optionally a number of effects can be applied to the composited output.
     // The chain of effects will be applied in the following order:
     // === Begin of effects ===
@@ -63,18 +63,17 @@
   };
 
   // This node is really a sentinel, and does not represent a real effect.
-  static EffectPaintPropertyNode* Root();
+  static const EffectPaintPropertyNode& Root();
 
-  static scoped_refptr<EffectPaintPropertyNode> Create(
-      scoped_refptr<const EffectPaintPropertyNode> parent,
+  static std::unique_ptr<EffectPaintPropertyNode> Create(
+      const EffectPaintPropertyNode& parent,
       State&& state) {
-    return base::AdoptRef(
-        new EffectPaintPropertyNode(std::move(parent), std::move(state)));
+    return base::WrapUnique(
+        new EffectPaintPropertyNode(&parent, std::move(state)));
   }
 
-  bool Update(scoped_refptr<const EffectPaintPropertyNode> parent,
-              State&& state) {
-    bool parent_changed = SetParent(parent);
+  bool Update(const EffectPaintPropertyNode& parent, State&& state) {
+    bool parent_changed = SetParent(&parent);
     if (state == state_)
       return parent_changed;
 
@@ -84,11 +83,9 @@
   }
 
   const TransformPaintPropertyNode* LocalTransformSpace() const {
-    return state_.local_transform_space.get();
+    return state_.local_transform_space;
   }
-  const ClipPaintPropertyNode* OutputClip() const {
-    return state_.output_clip.get();
-  }
+  const ClipPaintPropertyNode* OutputClip() const { return state_.output_clip; }
 
   SkBlendMode BlendMode() const { return state_.blend_mode; }
   float Opacity() const { return state_.opacity; }
@@ -121,8 +118,9 @@
 #if DCHECK_IS_ON()
   // The clone function is used by FindPropertiesNeedingUpdate.h for recording
   // an effect node before it has been updated, to later detect changes.
-  scoped_refptr<EffectPaintPropertyNode> Clone() const {
-    return base::AdoptRef(new EffectPaintPropertyNode(Parent(), State(state_)));
+  std::unique_ptr<EffectPaintPropertyNode> Clone() const {
+    return base::WrapUnique(
+        new EffectPaintPropertyNode(Parent(), State(state_)));
   }
 
   // The equality operator is used by FindPropertiesNeedingUpdate.h for checking
@@ -138,9 +136,8 @@
   size_t TreeMemoryUsageInBytes() const;
 
  private:
-  EffectPaintPropertyNode(scoped_refptr<const EffectPaintPropertyNode> parent,
-                          State&& state)
-      : PaintPropertyNode(std::move(parent)), state_(std::move(state)) {}
+  EffectPaintPropertyNode(const EffectPaintPropertyNode* parent, State&& state)
+      : PaintPropertyNode(parent), state_(std::move(state)) {}
 
   State state_;
 };
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
index 9e6dfbc..dd38b418 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
@@ -101,7 +101,7 @@
 
   // Case 3: Compute:
   // flatten(destination_to_screen)^-1 * flatten(source_to_screen)
-  const auto* root = TransformPaintPropertyNode::Root();
+  const auto* root = &TransformPaintPropertyNode::Root();
   success = true;
   if (source == root)
     return destination_cache.projection_from_screen();
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
index f334489..98c574f 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
@@ -157,8 +157,7 @@
 }
 
 TEST_P(GeometryMapperTest, IdentityTransform) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix());
+  auto transform = CreateTransform(t0(), TransformationMatrix());
   local_state.SetTransform(transform.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -169,8 +168,7 @@
 
 TEST_P(GeometryMapperTest, TranslationTransform) {
   expected_transform = TransformationMatrix().Translate(20, 10);
-  auto transform =
-      CreateTransform(TransformPaintPropertyNode::Root(), expected_transform);
+  auto transform = CreateTransform(t0(), expected_transform);
   local_state.SetTransform(transform.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -179,15 +177,13 @@
   CHECK_MAPPINGS();
 
   FloatRect rect = expected_transformed_rect;
-  GeometryMapper::SourceToDestinationRect(TransformPaintPropertyNode::Root(),
-                                          local_state.Transform(), rect);
+  GeometryMapper::SourceToDestinationRect(&t0(), local_state.Transform(), rect);
   EXPECT_FLOAT_RECT_NEAR(input_rect, rect);
 }
 
 TEST_P(GeometryMapperTest, RotationAndScaleTransform) {
   expected_transform = TransformationMatrix().Rotate(45).Scale(2);
-  auto transform =
-      CreateTransform(TransformPaintPropertyNode::Root(), expected_transform);
+  auto transform = CreateTransform(t0(), expected_transform);
   local_state.SetTransform(transform.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -199,8 +195,8 @@
 
 TEST_P(GeometryMapperTest, RotationAndScaleTransformWithTransformOrigin) {
   expected_transform = TransformationMatrix().Rotate(45).Scale(2);
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   expected_transform, FloatPoint3D(50, 50, 0));
+  auto transform =
+      CreateTransform(t0(), expected_transform, FloatPoint3D(50, 50, 0));
   local_state.SetTransform(transform.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -213,11 +209,10 @@
 
 TEST_P(GeometryMapperTest, NestedTransforms) {
   auto rotate_transform = TransformationMatrix().Rotate(45);
-  auto transform1 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform);
+  auto transform1 = CreateTransform(t0(), rotate_transform);
 
   auto scale_transform = TransformationMatrix().Scale(2);
-  auto transform2 = CreateTransform(transform1, scale_transform);
+  auto transform2 = CreateTransform(*transform1, scale_transform);
   local_state.SetTransform(transform2.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -231,14 +226,14 @@
 TEST_P(GeometryMapperTest, NestedTransformsFlattening) {
   TransformPaintPropertyNode::State rotate_transform;
   rotate_transform.matrix.Rotate3d(45, 0, 0);
-  auto transform1 = TransformPaintPropertyNode::Create(
-      TransformPaintPropertyNode::Root(), std::move(rotate_transform));
+  auto transform1 =
+      TransformPaintPropertyNode::Create(t0(), std::move(rotate_transform));
 
   TransformPaintPropertyNode::State inverse_rotate_transform;
   inverse_rotate_transform.matrix.Rotate3d(-45, 0, 0);
   inverse_rotate_transform.flattens_inherited_transform = true;
   auto transform2 = TransformPaintPropertyNode::Create(
-      transform1, std::move(inverse_rotate_transform));
+      *transform1, std::move(inverse_rotate_transform));
   local_state.SetTransform(transform2.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -254,11 +249,10 @@
 
 TEST_P(GeometryMapperTest, NestedTransformsScaleAndTranslation) {
   auto scale_transform = TransformationMatrix().Scale(2);
-  auto transform1 =
-      CreateTransform(TransformPaintPropertyNode::Root(), scale_transform);
+  auto transform1 = CreateTransform(t0(), scale_transform);
 
   auto translate_transform = TransformationMatrix().Translate(100, 0);
-  auto transform2 = CreateTransform(transform1, translate_transform);
+  auto transform2 = CreateTransform(*transform1, translate_transform);
   local_state.SetTransform(transform2.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -273,11 +267,10 @@
 
 TEST_P(GeometryMapperTest, NestedTransformsIntermediateDestination) {
   auto rotate_transform = TransformationMatrix().Rotate(45);
-  auto transform1 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform);
+  auto transform1 = CreateTransform(t0(), rotate_transform);
 
   auto scale_transform = TransformationMatrix().Translate(10, 20);
-  auto transform2 = CreateTransform(transform1, scale_transform);
+  auto transform2 = CreateTransform(*transform1, scale_transform);
 
   local_state.SetTransform(transform2.get());
   ancestor_state.SetTransform(transform1.get());
@@ -290,9 +283,7 @@
 }
 
 TEST_P(GeometryMapperTest, SimpleClip) {
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 10, 50, 50));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 50, 50));
   local_state.SetClip(clip.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -304,12 +295,11 @@
 
 TEST_P(GeometryMapperTest, SimpleClipOverlayScrollbars) {
   ClipPaintPropertyNode::State clip_state;
-  clip_state.local_transform_space = TransformPaintPropertyNode::Root();
+  clip_state.local_transform_space = &t0();
   clip_state.clip_rect = FloatRoundedRect(10, 10, 50, 50);
   clip_state.clip_rect_excluding_overlay_scrollbars =
       FloatRoundedRect(10, 10, 45, 43);
-  auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
-                                            std::move(clip_state));
+  auto clip = ClipPaintPropertyNode::Create(c0(), std::move(clip_state));
   local_state.SetClip(clip.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -344,12 +334,7 @@
 }
 
 TEST_P(GeometryMapperTest, SimpleClipInclusiveIntersect) {
-  ClipPaintPropertyNode::State clip_state;
-  clip_state.local_transform_space = TransformPaintPropertyNode::Root();
-  clip_state.clip_rect = FloatRoundedRect(10, 10, 50, 50);
-  auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
-                                            std::move(clip_state));
-
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 50, 50));
   local_state.SetClip(clip.get());
 
   FloatClipRect actual_clip_rect(FloatRect(60, 10, 10, 10));
@@ -372,8 +357,7 @@
   FloatRoundedRect rect(FloatRect(10, 10, 50, 50),
                         FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(),
                                                 FloatSize(), FloatSize()));
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(), rect);
+  auto clip = CreateClip(c0(), &t0(), rect);
   local_state.SetClip(clip.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -388,9 +372,7 @@
   FloatRoundedRect rect(FloatRect(10, 10, 50, 50),
                         FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(),
                                                 FloatSize(), FloatSize()));
-  auto clip = CreateClipPathClip(ClipPaintPropertyNode::Root(),
-                                 TransformPaintPropertyNode::Root(),
-                                 FloatRoundedRect(10, 10, 50, 50));
+  auto clip = CreateClipPathClip(c0(), &t0(), FloatRoundedRect(10, 10, 50, 50));
   local_state.SetClip(clip.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -407,11 +389,8 @@
       FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(), FloatSize(),
                               FloatSize()));
 
-  auto clip1 = CreateClip(ClipPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(), clip_rect1);
-
-  auto clip2 = CreateClip(clip1, TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(10, 10, 50, 50));
+  auto clip1 = CreateClip(c0(), &t0(), clip_rect1);
+  auto clip2 = CreateClip(*clip1, &t0(), FloatRoundedRect(10, 10, 50, 50));
   local_state.SetClip(clip2.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -428,19 +407,16 @@
 }
 
 TEST_P(GeometryMapperTest, TwoClipsTransformAbove) {
-  auto transform = CreateTransform(TransformPaintPropertyNode::Root(),
-                                   TransformationMatrix());
+  auto transform = CreateTransform(t0(), TransformationMatrix());
 
   FloatRoundedRect clip_rect1(
       FloatRect(10, 10, 50, 50),
       FloatRoundedRect::Radii(FloatSize(1, 1), FloatSize(), FloatSize(),
                               FloatSize()));
 
-  auto clip1 =
-      CreateClip(ClipPaintPropertyNode::Root(), transform.get(), clip_rect1);
-
+  auto clip1 = CreateClip(c0(), transform.get(), clip_rect1);
   auto clip2 =
-      CreateClip(clip1, transform.get(), FloatRoundedRect(10, 10, 30, 40));
+      CreateClip(*clip1, transform.get(), FloatRoundedRect(10, 10, 30, 40));
   local_state.SetClip(clip2.get());
 
   input_rect = FloatRect(0, 0, 100, 100);
@@ -459,10 +435,9 @@
 
 TEST_P(GeometryMapperTest, ClipBeforeTransform) {
   expected_transform = TransformationMatrix().Rotate(45);
-  auto transform =
-      CreateTransform(TransformPaintPropertyNode::Root(), expected_transform);
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(), transform.get(),
-                         FloatRoundedRect(10, 10, 50, 50));
+  auto transform = CreateTransform(t0(), expected_transform);
+  auto clip =
+      CreateClip(c0(), transform.get(), FloatRoundedRect(10, 10, 50, 50));
   local_state.SetClip(clip.get());
   local_state.SetTransform(transform.get());
 
@@ -480,11 +455,8 @@
 
 TEST_P(GeometryMapperTest, ClipAfterTransform) {
   expected_transform = TransformationMatrix().Rotate(45);
-  auto transform =
-      CreateTransform(TransformPaintPropertyNode::Root(), expected_transform);
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 10, 200, 200));
+  auto transform = CreateTransform(t0(), expected_transform);
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 200, 200));
   local_state.SetClip(clip.get());
   local_state.SetTransform(transform.get());
 
@@ -500,16 +472,11 @@
 }
 
 TEST_P(GeometryMapperTest, TwoClipsWithTransformBetween) {
-  auto clip1 = CreateClip(ClipPaintPropertyNode::Root(),
-                          TransformPaintPropertyNode::Root(),
-                          FloatRoundedRect(10, 10, 200, 200));
-
+  auto clip1 = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 200, 200));
   expected_transform = TransformationMatrix().Rotate(45);
-  auto transform =
-      CreateTransform(TransformPaintPropertyNode::Root(), expected_transform);
-
+  auto transform = CreateTransform(t0(), expected_transform);
   auto clip2 =
-      CreateClip(clip1, transform.get(), FloatRoundedRect(10, 10, 200, 200));
+      CreateClip(*clip1, transform.get(), FloatRoundedRect(10, 10, 200, 200));
 
   input_rect = FloatRect(0, 0, 100, 100);
   expected_transformed_rect = expected_transform.MapRect(input_rect);
@@ -553,12 +520,10 @@
   // These transforms are siblings. Thus mapping from one to the other requires
   // going through the root.
   auto rotate_transform1 = TransformationMatrix().Rotate(45);
-  auto transform1 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform1);
+  auto transform1 = CreateTransform(t0(), rotate_transform1);
 
   auto rotate_transform2 = TransformationMatrix().Rotate(-45);
-  auto transform2 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform2);
+  auto transform2 = CreateTransform(t0(), rotate_transform2);
 
   auto transform1_state = PropertyTreeState::Root();
   transform1_state.SetTransform(transform1.get());
@@ -597,15 +562,13 @@
   // These transforms are siblings. Thus mapping from one to the other requires
   // going through the root.
   auto rotate_transform1 = TransformationMatrix().Rotate(45);
-  auto transform1 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform1);
+  auto transform1 = CreateTransform(t0(), rotate_transform1);
 
   auto rotate_transform2 = TransformationMatrix().Rotate(-45);
-  auto transform2 =
-      CreateTransform(TransformPaintPropertyNode::Root(), rotate_transform2);
+  auto transform2 = CreateTransform(t0(), rotate_transform2);
 
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(), transform2.get(),
-                         FloatRoundedRect(10, 20, 30, 40));
+  auto clip =
+      CreateClip(c0(), transform2.get(), FloatRoundedRect(10, 20, 30, 40));
 
   auto transform1_state = PropertyTreeState::Root();
   transform1_state.SetTransform(transform1.get());
@@ -642,24 +605,22 @@
 
 TEST_P(GeometryMapperTest, FilterWithClipsAndTransforms) {
   auto transform_above_effect =
-      CreateTransform(TransformPaintPropertyNode::Root(),
-                      TransformationMatrix().Translate(40, 50));
+      CreateTransform(t0(), TransformationMatrix().Translate(40, 50));
   auto transform_below_effect = CreateTransform(
-      transform_above_effect, TransformationMatrix().Translate(20, 30));
+      *transform_above_effect, TransformationMatrix().Translate(20, 30));
 
   // This clip is between transformAboveEffect and the effect.
-  auto clip_above_effect =
-      CreateClip(ClipPaintPropertyNode::Root(), transform_above_effect,
-                 FloatRoundedRect(-100, -100, 200, 200));
+  auto clip_above_effect = CreateClip(c0(), transform_above_effect.get(),
+                                      FloatRoundedRect(-100, -100, 200, 200));
   // This clip is between the effect and transformBelowEffect.
-  auto clip_below_effect = CreateClip(clip_above_effect, transform_above_effect,
-                                      FloatRoundedRect(10, 10, 100, 100));
+  auto clip_below_effect =
+      CreateClip(*clip_above_effect, transform_above_effect.get(),
+                 FloatRoundedRect(10, 10, 100, 100));
 
   CompositorFilterOperations filters;
   filters.AppendBlurFilter(20);
-  auto effect =
-      CreateFilterEffect(EffectPaintPropertyNode::Root(),
-                         transform_above_effect, clip_above_effect, filters);
+  auto effect = CreateFilterEffect(e0(), transform_above_effect.get(),
+                                   clip_above_effect.get(), filters);
 
   local_state = PropertyTreeState(transform_below_effect.get(),
                                   clip_below_effect.get(), effect.get());
@@ -694,8 +655,7 @@
   CompositorFilterOperations filters;
   filters.AppendReferenceFilter(PaintFilterBuilder::BuildBoxReflectFilter(
       BoxReflection(BoxReflection::kHorizontalReflection, 0), nullptr));
-  auto effect = CreateFilterEffect(EffectPaintPropertyNode::Root(), filters,
-                                   FloatPoint(100, 100));
+  auto effect = CreateFilterEffect(e0(), filters, FloatPoint(100, 100));
   local_state.SetEffect(effect.get());
 
   input_rect = FloatRect(100, 100, 50, 50);
@@ -712,12 +672,8 @@
   if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
     return;
 
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 10, 50, 50));
-
-  PropertyTreeState dest(TransformPaintPropertyNode::Root(), clip.get(),
-                         EffectPaintPropertyNode::Root());
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 50, 50));
+  PropertyTreeState dest(&t0(), clip.get(), &e0());
 
   FloatClipRect visual_rect(FloatRect(0, 0, 10, 200));
   GeometryMapper::LocalToAncestorVisualRect(PropertyTreeState::Root(), dest,
@@ -731,16 +687,10 @@
 }
 
 TEST_P(GeometryMapperTest, PointVisibleInAncestorSpaceSimpleClip) {
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(10, 10, 50, 50));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(10, 10, 50, 50));
 
-  PropertyTreeState local_state(TransformPaintPropertyNode::Root(), clip.get(),
-                                EffectPaintPropertyNode::Root());
-
-  PropertyTreeState ancestor_state(TransformPaintPropertyNode::Root(),
-                                   ClipPaintPropertyNode::Root(),
-                                   EffectPaintPropertyNode::Root());
+  PropertyTreeState local_state(&t0(), clip.get(), &e0());
+  PropertyTreeState ancestor_state = PropertyTreeState::Root();
 
   EXPECT_TRUE(GeometryMapper::PointVisibleInAncestorSpace(
       local_state, ancestor_state, FloatPoint(30, 30)));
@@ -757,15 +707,10 @@
   FloatRoundedRect::Radii radii;
   radii.SetTopLeft(FloatSize(8, 8));
   clip_rect.SetRadii(radii);
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(), clip_rect);
+  auto clip = CreateClip(c0(), &t0(), clip_rect);
 
-  PropertyTreeState local_state(TransformPaintPropertyNode::Root(), clip.get(),
-                                EffectPaintPropertyNode::Root());
-
-  PropertyTreeState ancestor_state(TransformPaintPropertyNode::Root(),
-                                   ClipPaintPropertyNode::Root(),
-                                   EffectPaintPropertyNode::Root());
+  PropertyTreeState local_state(&t0(), clip.get(), &e0());
+  PropertyTreeState ancestor_state = PropertyTreeState::Root();
 
   EXPECT_TRUE(GeometryMapper::PointVisibleInAncestorSpace(
       local_state, ancestor_state, FloatPoint(30, 30)));
@@ -787,18 +732,13 @@
   path->AddLineTo(FloatPoint(10, 10));
 
   ClipPaintPropertyNode::State state;
-  state.local_transform_space = TransformPaintPropertyNode::Root();
+  state.local_transform_space = &t0();
   state.clip_rect = FloatRoundedRect(FloatRect(0, 0, 500, 500));
   state.clip_path = base::AdoptRef(path);
-  auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
-                                            std::move(state));
+  auto clip = ClipPaintPropertyNode::Create(c0(), std::move(state));
 
-  PropertyTreeState local_state(TransformPaintPropertyNode::Root(), clip.get(),
-                                EffectPaintPropertyNode::Root());
-
-  PropertyTreeState ancestor_state(TransformPaintPropertyNode::Root(),
-                                   ClipPaintPropertyNode::Root(),
-                                   EffectPaintPropertyNode::Root());
+  PropertyTreeState local_state(&t0(), clip.get(), &e0());
+  PropertyTreeState ancestor_state = PropertyTreeState::Root();
 
   EXPECT_TRUE(GeometryMapper::PointVisibleInAncestorSpace(
       local_state, ancestor_state, FloatPoint(30, 30)));
@@ -811,21 +751,13 @@
 }
 
 TEST_P(GeometryMapperTest, PointVisibleInAncestorSpaceSimpleClipWithTransform) {
-  TransformPaintPropertyNode::State translate_transform;
-  translate_transform.matrix.Translate(10, 10);
-  auto transform = TransformPaintPropertyNode::Create(
-      TransformPaintPropertyNode::Root(), std::move(translate_transform));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(10, 10));
+  auto clip =
+      CreateClip(c0(), &t0(), FloatRoundedRect(FloatRect(20, 20, 50, 50)));
 
-  auto clip = CreateClip(ClipPaintPropertyNode::Root(),
-                         TransformPaintPropertyNode::Root(),
-                         FloatRoundedRect(FloatRect(20, 20, 50, 50)));
-
-  PropertyTreeState local_state(transform.get(), clip.get(),
-                                EffectPaintPropertyNode::Root());
-
-  PropertyTreeState ancestor_state(TransformPaintPropertyNode::Root(),
-                                   ClipPaintPropertyNode::Root(),
-                                   EffectPaintPropertyNode::Root());
+  PropertyTreeState local_state(transform.get(), clip.get(), &e0());
+  PropertyTreeState ancestor_state = PropertyTreeState::Root();
 
   EXPECT_TRUE(GeometryMapper::PointVisibleInAncestorSpace(
       local_state, ancestor_state, FloatPoint(30, 30)));
@@ -838,10 +770,8 @@
 }
 
 TEST_P(GeometryMapperTest, PointVisibleInAncestorSpaceClipPathWithTransform) {
-  TransformPaintPropertyNode::State translate_transform;
-  translate_transform.matrix.Translate(10, 10);
-  auto transform = TransformPaintPropertyNode::Create(
-      TransformPaintPropertyNode::Root(), std::move(translate_transform));
+  auto transform =
+      CreateTransform(t0(), TransformationMatrix().Translate(10, 10));
 
   RefCountedPath* path = new RefCountedPath;
   path->MoveTo(FloatPoint(20, 20));
@@ -851,18 +781,13 @@
   path->AddLineTo(FloatPoint(20, 20));
 
   ClipPaintPropertyNode::State state;
-  state.local_transform_space = TransformPaintPropertyNode::Root();
+  state.local_transform_space = &t0();
   state.clip_rect = FloatRoundedRect(FloatRect(0, 0, 500, 500));
   state.clip_path = base::AdoptRef(path);
-  auto clip = ClipPaintPropertyNode::Create(ClipPaintPropertyNode::Root(),
-                                            std::move(state));
+  auto clip = ClipPaintPropertyNode::Create(c0(), std::move(state));
 
-  PropertyTreeState local_state(transform.get(), clip.get(),
-                                EffectPaintPropertyNode::Root());
-
-  PropertyTreeState ancestor_state(TransformPaintPropertyNode::Root(),
-                                   ClipPaintPropertyNode::Root(),
-                                   EffectPaintPropertyNode::Root());
+  PropertyTreeState local_state(transform.get(), clip.get(), &e0());
+  PropertyTreeState ancestor_state = PropertyTreeState::Root();
 
   EXPECT_TRUE(GeometryMapper::PointVisibleInAncestorSpace(
       local_state, ancestor_state, FloatPoint(30, 30)));
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
index 4504b5e2..c0a28b6 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunk.h
@@ -10,8 +10,8 @@
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item.h"
 #include "third_party/blink/renderer/platform/graphics/paint/hit_test_data.h"
+#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
 #include "third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.h"
-#include "third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
@@ -100,7 +100,7 @@
   Id id;
 
   // The paint properties which apply to this chunk.
-  RefCountedPropertyTreeState properties;
+  PropertyTreeState properties;
 
   // The total bounds of this paint chunk's contents, in the coordinate space of
   // the containing transform node.
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
index d74be6e9..104fccdf 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
@@ -100,7 +100,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto simple_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
   auto simple_transform = DefaultPaintChunkProperties();
   simple_transform.SetTransform(simple_transform_node.get());
 
@@ -109,7 +109,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto another_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
   auto another_transform = DefaultPaintChunkProperties();
   another_transform.SetTransform(another_transform_node.get());
   PaintChunk::Id id3(client_, DisplayItemType(3));
@@ -130,7 +130,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto simple_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 0, 0, 0, 0, 0), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 0, 0, 0, 0, 0), FloatPoint3D(9, 8, 7));
   auto simple_transform = DefaultPaintChunkProperties();
   simple_transform.SetTransform(simple_transform_node.get());
   PaintChunk::Id id2(client_, DisplayItemType(2));
@@ -138,8 +138,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
-  auto simple_effect_node =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5f);
+  auto simple_effect_node = CreateOpacityEffect(e0(), 0.5f);
   auto simple_transform_and_effect = DefaultPaintChunkProperties();
   simple_transform_and_effect.SetTransform(simple_transform_node.get());
   simple_transform_and_effect.SetEffect(simple_effect_node.get());
@@ -149,11 +148,10 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto new_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(1, 1, 0, 0, 0, 0), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(1, 1, 0, 0, 0, 0), FloatPoint3D(9, 8, 7));
   auto simple_transform_and_effect_with_updated_transform =
       DefaultPaintChunkProperties();
-  auto new_effect_node =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5f);
+  auto new_effect_node = CreateOpacityEffect(e0(), 0.5f);
   simple_transform_and_effect_with_updated_transform.SetTransform(
       new_transform_node.get());
   simple_transform_and_effect_with_updated_transform.SetEffect(
@@ -200,7 +198,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto simple_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
   auto simple_transform = DefaultPaintChunkProperties();
   simple_transform.SetTransform(simple_transform_node.get());
   PaintChunk::Id id2(client_, DisplayItemType(2));
@@ -229,14 +227,14 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto first_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
   auto first_transform = DefaultPaintChunkProperties();
   first_transform.SetTransform(first_transform_node.get());
   PaintChunk::Id id2(client_, DisplayItemType(2));
   chunker.UpdateCurrentPaintChunkProperties(base::nullopt, first_transform);
 
   auto second_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(9, 8, 7, 6, 5, 4), FloatPoint3D(3, 2, 1));
+      t0(), TransformationMatrix(9, 8, 7, 6, 5, 4), FloatPoint3D(3, 2, 1));
   auto second_transform = DefaultPaintChunkProperties();
   second_transform.SetTransform(second_transform_node.get());
   PaintChunk::Id id3(client_, DisplayItemType(3));
@@ -407,7 +405,7 @@
   chunker.IncrementDisplayItemIndex(TestChunkerDisplayItem(client_));
 
   auto simple_transform_node = CreateTransform(
-      nullptr, TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
+      t0(), TransformationMatrix(0, 1, 2, 3, 4, 5), FloatPoint3D(9, 8, 7));
   auto simple_transform = DefaultPaintChunkProperties();
   simple_transform.SetTransform(simple_transform_node.get());
   PaintChunk::Id id2(client_, DisplayItemType(2));
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index 16706fc..bcfa96f 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -517,7 +517,7 @@
     properties_before_subsequence =
         new_paint_chunks_.CurrentPaintChunkProperties();
     UpdateCurrentPaintChunkPropertiesUsingIdWithFragment(
-        cached_chunk->id, cached_chunk->properties.GetPropertyTreeState());
+        cached_chunk->id, cached_chunk->properties);
   } else {
     // Avoid uninitialized variable error on Windows.
     cached_chunk = current_paint_artifact_.PaintChunks().begin();
@@ -537,7 +537,7 @@
       DCHECK(cached_chunk != current_paint_artifact_.PaintChunks().end());
       new_paint_chunks_.ForceNewChunk();
       UpdateCurrentPaintChunkPropertiesUsingIdWithFragment(
-          cached_chunk->id, cached_chunk->properties.GetPropertyTreeState());
+          cached_chunk->id, cached_chunk->properties);
     }
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
index 0689945..bb1ce228 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_test.cc
@@ -482,7 +482,7 @@
   FakeDisplayItemClient second("second", LayoutRect(100, 100, 200, 200));
   GraphicsContext context(GetPaintController());
 
-  auto clip = CreateClip(nullptr, nullptr, FloatRoundedRect(1, 1, 2, 2));
+  auto clip = CreateClip(c0(), &t0(), FloatRoundedRect(1, 1, 2, 2));
   auto properties = DefaultPaintChunkProperties();
   properties.SetClip(clip.get());
   GetPaintController().UpdateCurrentPaintChunkProperties(
@@ -522,7 +522,7 @@
   second.SetDisplayItemsUncached();
   DrawRect(context, first, kBackgroundType, FloatRect(100, 100, 150, 150));
 
-  auto clip2 = CreateClip(nullptr, nullptr, FloatRoundedRect(1, 1, 2, 2));
+  auto clip2 = CreateClip(c0(), &t0(), FloatRoundedRect(1, 1, 2, 2));
   auto properties2 = DefaultPaintChunkProperties();
   properties2.SetClip(clip2.get());
   GetPaintController().UpdateCurrentPaintChunkProperties(
@@ -798,13 +798,11 @@
   FakeDisplayItemClient content2("content2", LayoutRect(100, 200, 50, 200));
   GraphicsContext context(GetPaintController());
 
-  auto container1_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto container1_effect = CreateOpacityEffect(e0(), 0.5);
   auto container1_properties = DefaultPaintChunkProperties();
   container1_properties.SetEffect(container1_effect.get());
 
-  auto container2_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto container2_effect = CreateOpacityEffect(e0(), 0.5);
   auto container2_properties = DefaultPaintChunkProperties();
   container2_properties.SetEffect(container2_effect.get());
 
@@ -1117,13 +1115,11 @@
   FakeDisplayItemClient content2("content2", LayoutRect(100, 200, 50, 200));
   GraphicsContext context(GetPaintController());
 
-  auto container1_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto container1_effect = CreateOpacityEffect(e0(), 0.5);
   auto container1_properties = DefaultPaintChunkProperties();
   container1_properties.SetEffect(container1_effect.get());
 
-  auto container2_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto container2_effect = CreateOpacityEffect(e0(), 0.5);
   auto container2_properties = DefaultPaintChunkProperties();
   container2_properties.SetEffect(container2_effect.get());
 
@@ -1230,25 +1226,21 @@
   FakeDisplayItemClient content2("content2", LayoutRect(100, 200, 50, 200));
   GraphicsContext context(GetPaintController());
 
-  auto container1_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.5);
+  auto container1_effect = CreateOpacityEffect(e0(), 0.5);
   auto container1_background_properties = DefaultPaintChunkProperties();
   container1_background_properties.SetEffect(container1_effect.get());
   auto container1_foreground_properties = DefaultPaintChunkProperties();
   container1_foreground_properties.SetEffect(container1_effect.get());
 
-  auto content1_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.6);
+  auto content1_effect = CreateOpacityEffect(e0(), 0.6);
   auto content1_properties = DefaultPaintChunkProperties();
   content1_properties.SetEffect(content1_effect.get());
 
-  auto container2_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.7);
+  auto container2_effect = CreateOpacityEffect(e0(), 0.7);
   auto container2_background_properties = DefaultPaintChunkProperties();
   container2_background_properties.SetEffect(container2_effect.get());
 
-  auto content2_effect =
-      CreateOpacityEffect(EffectPaintPropertyNode::Root(), 0.8);
+  auto content2_effect = CreateOpacityEffect(e0(), 0.8);
   auto content2_properties = DefaultPaintChunkProperties();
   content2_properties.SetEffect(content2_effect.get());
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
index cd010c4..4f4cd57 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
@@ -5,10 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_PROPERTY_NODE_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_PROPERTY_NODE_H_
 
-#include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/platform/json/json_values.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 #if DCHECK_IS_ON()
@@ -55,10 +53,12 @@
     const TransformPaintPropertyNode&);
 
 template <typename NodeType>
-class PaintPropertyNode : public RefCounted<NodeType> {
+class PaintPropertyNode {
+  USING_FAST_MALLOC(NodeType);
+
  public:
   // Parent property node, or nullptr if this is the root node.
-  const NodeType* Parent() const { return parent_.get(); }
+  const NodeType* Parent() const { return parent_; }
   bool IsRoot() const { return !parent_; }
 
   bool IsAncestorOf(const NodeType& other) const {
@@ -115,24 +115,23 @@
 #endif
 
  protected:
-  PaintPropertyNode(scoped_refptr<const NodeType> parent)
-      : parent_(std::move(parent)) {}
+  PaintPropertyNode(const NodeType* parent) : parent_(parent) {}
 
-  bool SetParent(scoped_refptr<const NodeType> parent) {
+  bool SetParent(const NodeType* parent) {
     DCHECK(!IsRoot());
     DCHECK(parent != this);
     if (parent == parent_)
       return false;
 
     SetChanged();
-    parent_ = std::move(parent);
+    parent_ = parent;
     return true;
   }
 
   void SetChanged() { changed_ = true; }
 
  private:
-  scoped_refptr<const NodeType> parent_;
+  const NodeType* parent_;
   mutable bool changed_ = true;
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
index 88820aa6..072d424 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_property_node_test.cc
@@ -14,12 +14,12 @@
 class PaintPropertyNodeTest : public testing::Test {
  protected:
   void SetUp() override {
-    root = ClipPaintPropertyNode::Root();
-    node = CreateClip(root, nullptr, FloatRoundedRect());
-    child1 = CreateClip(node, nullptr, FloatRoundedRect());
-    child2 = CreateClip(node, nullptr, FloatRoundedRect());
-    grandchild1 = CreateClip(child1, nullptr, FloatRoundedRect());
-    grandchild2 = CreateClip(child2, nullptr, FloatRoundedRect());
+    root = &ClipPaintPropertyNode::Root();
+    node = CreateClip(*root, nullptr, FloatRoundedRect());
+    child1 = CreateClip(*node, nullptr, FloatRoundedRect());
+    child2 = CreateClip(*node, nullptr, FloatRoundedRect());
+    grandchild1 = CreateClip(*child1, nullptr, FloatRoundedRect());
+    grandchild2 = CreateClip(*child2, nullptr, FloatRoundedRect());
 
     //          root
     //           |
@@ -35,10 +35,10 @@
     grandchild2->ClearChangedToRoot();
   }
 
-  static void Update(scoped_refptr<ClipPaintPropertyNode> node,
-                     scoped_refptr<const ClipPaintPropertyNode> new_parent,
+  static void Update(std::unique_ptr<ClipPaintPropertyNode>& node,
+                     const ClipPaintPropertyNode& new_parent,
                      const FloatRoundedRect& new_clip_rect) {
-    node->Update(std::move(new_parent),
+    node->Update(new_parent,
                  ClipPaintPropertyNode::State{nullptr, new_clip_rect});
   }
 
@@ -60,30 +60,30 @@
     EXPECT_FALSE(grandchild2->Changed(*root));
   }
 
-  scoped_refptr<ClipPaintPropertyNode> root;
-  scoped_refptr<ClipPaintPropertyNode> node;
-  scoped_refptr<ClipPaintPropertyNode> child1;
-  scoped_refptr<ClipPaintPropertyNode> child2;
-  scoped_refptr<ClipPaintPropertyNode> grandchild1;
-  scoped_refptr<ClipPaintPropertyNode> grandchild2;
+  const ClipPaintPropertyNode* root;
+  std::unique_ptr<ClipPaintPropertyNode> node;
+  std::unique_ptr<ClipPaintPropertyNode> child1;
+  std::unique_ptr<ClipPaintPropertyNode> child2;
+  std::unique_ptr<ClipPaintPropertyNode> grandchild1;
+  std::unique_ptr<ClipPaintPropertyNode> grandchild2;
 };
 
 TEST_F(PaintPropertyNodeTest, LowestCommonAncestor) {
-  EXPECT_EQ(node, &LowestCommonAncestor(*node, *node));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*node, *node));
   EXPECT_EQ(root, &LowestCommonAncestor(*root, *root));
 
-  EXPECT_EQ(node, &LowestCommonAncestor(*grandchild1, *grandchild2));
-  EXPECT_EQ(node, &LowestCommonAncestor(*grandchild1, *child2));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*grandchild1, *grandchild2));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*grandchild1, *child2));
   EXPECT_EQ(root, &LowestCommonAncestor(*grandchild1, *root));
-  EXPECT_EQ(child1, &LowestCommonAncestor(*grandchild1, *child1));
+  EXPECT_EQ(child1.get(), &LowestCommonAncestor(*grandchild1, *child1));
 
-  EXPECT_EQ(node, &LowestCommonAncestor(*grandchild2, *grandchild1));
-  EXPECT_EQ(node, &LowestCommonAncestor(*grandchild2, *child1));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*grandchild2, *grandchild1));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*grandchild2, *child1));
   EXPECT_EQ(root, &LowestCommonAncestor(*grandchild2, *root));
-  EXPECT_EQ(child2, &LowestCommonAncestor(*grandchild2, *child2));
+  EXPECT_EQ(child2.get(), &LowestCommonAncestor(*grandchild2, *child2));
 
-  EXPECT_EQ(node, &LowestCommonAncestor(*child1, *child2));
-  EXPECT_EQ(node, &LowestCommonAncestor(*child2, *child1));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*child1, *child2));
+  EXPECT_EQ(node.get(), &LowestCommonAncestor(*child2, *child1));
 }
 
 TEST_F(PaintPropertyNodeTest, InitialStateAndReset) {
@@ -94,7 +94,7 @@
 
 TEST_F(PaintPropertyNodeTest, ChangeNode) {
   ResetAllChanged();
-  Update(node, root, FloatRoundedRect(1, 2, 3, 4));
+  Update(node, *root, FloatRoundedRect(1, 2, 3, 4));
   EXPECT_TRUE(node->Changed(*root));
   EXPECT_FALSE(node->Changed(*node));
   EXPECT_TRUE(child1->Changed(*root));
@@ -111,7 +111,7 @@
 
 TEST_F(PaintPropertyNodeTest, ChangeOneChild) {
   ResetAllChanged();
-  Update(child1, node, FloatRoundedRect(1, 2, 3, 4));
+  Update(child1, *node, FloatRoundedRect(1, 2, 3, 4));
   EXPECT_FALSE(node->Changed(*root));
   EXPECT_FALSE(node->Changed(*node));
   EXPECT_TRUE(child1->Changed(*root));
@@ -136,7 +136,7 @@
 
 TEST_F(PaintPropertyNodeTest, Reparent) {
   ResetAllChanged();
-  Update(child1, child2, FloatRoundedRect(1, 2, 3, 4));
+  Update(child1, *child2, FloatRoundedRect(1, 2, 3, 4));
   EXPECT_FALSE(node->Changed(*root));
   EXPECT_TRUE(child1->Changed(*node));
   EXPECT_TRUE(child1->Changed(*child2));
diff --git a/third_party/blink/renderer/platform/graphics/paint/property_tree_state.cc b/third_party/blink/renderer/platform/graphics/paint/property_tree_state.cc
index 736b61c..8b0bfc87 100644
--- a/third_party/blink/renderer/platform/graphics/paint/property_tree_state.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/property_tree_state.cc
@@ -10,11 +10,10 @@
 
 const PropertyTreeState& PropertyTreeState::Root() {
   DEFINE_STATIC_LOCAL(
-      std::unique_ptr<PropertyTreeState>, root,
-      (std::make_unique<PropertyTreeState>(TransformPaintPropertyNode::Root(),
-                                           ClipPaintPropertyNode::Root(),
-                                           EffectPaintPropertyNode::Root())));
-  return *root;
+      PropertyTreeState, root,
+      (&TransformPaintPropertyNode::Root(), &ClipPaintPropertyNode::Root(),
+       &EffectPaintPropertyNode::Root()));
+  return root;
 }
 
 const CompositorElementId PropertyTreeState::GetCompositorElementId(
diff --git a/third_party/blink/renderer/platform/graphics/paint/property_tree_state_test.cc b/third_party/blink/renderer/platform/graphics/paint/property_tree_state_test.cc
index 26bf33b..6a91f02 100644
--- a/third_party/blink/renderer/platform/graphics/paint/property_tree_state_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/property_tree_state_test.cc
@@ -10,7 +10,7 @@
 
 class PropertyTreeStateTest : public testing::Test {};
 
-static scoped_refptr<TransformPaintPropertyNode>
+static std::unique_ptr<TransformPaintPropertyNode>
 CreateTransformWithCompositorElementId(
     const CompositorElementId& compositor_element_id) {
   TransformPaintPropertyNode::State state;
@@ -19,7 +19,7 @@
                                             std::move(state));
 }
 
-static scoped_refptr<EffectPaintPropertyNode>
+static std::unique_ptr<EffectPaintPropertyNode>
 CreateEffectWithCompositorElementId(
     const CompositorElementId& compositor_element_id) {
   EffectPaintPropertyNode::State state;
@@ -29,40 +29,38 @@
 }
 
 TEST_F(PropertyTreeStateTest, CompositorElementIdNoElementIdOnAnyNode) {
-  PropertyTreeState state(TransformPaintPropertyNode::Root(),
-                          ClipPaintPropertyNode::Root(),
-                          EffectPaintPropertyNode::Root());
   EXPECT_EQ(CompositorElementId(),
-            state.GetCompositorElementId(CompositorElementIdSet()));
+            PropertyTreeState::Root().GetCompositorElementId(
+                CompositorElementIdSet()));
 }
 
 TEST_F(PropertyTreeStateTest, CompositorElementIdWithElementIdOnTransformNode) {
   CompositorElementId expected_compositor_element_id = CompositorElementId(2);
-  scoped_refptr<TransformPaintPropertyNode> transform =
+  auto transform =
       CreateTransformWithCompositorElementId(expected_compositor_element_id);
-  PropertyTreeState state(transform.get(), ClipPaintPropertyNode::Root(),
-                          EffectPaintPropertyNode::Root());
+  PropertyTreeState state(transform.get(), &ClipPaintPropertyNode::Root(),
+                          &EffectPaintPropertyNode::Root());
   EXPECT_EQ(expected_compositor_element_id,
             state.GetCompositorElementId(CompositorElementIdSet()));
 }
 
 TEST_F(PropertyTreeStateTest, CompositorElementIdWithElementIdOnEffectNode) {
   CompositorElementId expected_compositor_element_id = CompositorElementId(2);
-  scoped_refptr<EffectPaintPropertyNode> effect =
+  auto effect =
       CreateEffectWithCompositorElementId(expected_compositor_element_id);
-  PropertyTreeState state(TransformPaintPropertyNode::Root(),
-                          ClipPaintPropertyNode::Root(), effect.get());
+  PropertyTreeState state(&TransformPaintPropertyNode::Root(),
+                          &ClipPaintPropertyNode::Root(), effect.get());
   EXPECT_EQ(expected_compositor_element_id,
             state.GetCompositorElementId(CompositorElementIdSet()));
 }
 
 TEST_F(PropertyTreeStateTest, CompositorElementIdWithElementIdOnMultipleNodes) {
   CompositorElementId expected_compositor_element_id = CompositorElementId(2);
-  scoped_refptr<TransformPaintPropertyNode> transform =
+  auto transform =
       CreateTransformWithCompositorElementId(expected_compositor_element_id);
-  scoped_refptr<EffectPaintPropertyNode> effect =
+  auto effect =
       CreateEffectWithCompositorElementId(expected_compositor_element_id);
-  PropertyTreeState state(transform.get(), ClipPaintPropertyNode::Root(),
+  PropertyTreeState state(transform.get(), &ClipPaintPropertyNode::Root(),
                           effect.get());
   EXPECT_EQ(expected_compositor_element_id,
             state.GetCompositorElementId(CompositorElementIdSet()));
@@ -71,11 +69,11 @@
 TEST_F(PropertyTreeStateTest, CompositorElementIdWithDifferingElementIds) {
   CompositorElementId first_compositor_element_id = CompositorElementId(2);
   CompositorElementId second_compositor_element_id = CompositorElementId(3);
-  scoped_refptr<TransformPaintPropertyNode> transform =
+  auto transform =
       CreateTransformWithCompositorElementId(first_compositor_element_id);
-  scoped_refptr<EffectPaintPropertyNode> effect =
+  auto effect =
       CreateEffectWithCompositorElementId(second_compositor_element_id);
-  PropertyTreeState state(transform.get(), ClipPaintPropertyNode::Root(),
+  PropertyTreeState state(transform.get(), &ClipPaintPropertyNode::Root(),
                           effect.get());
 
   CompositorElementIdSet composited_element_ids;
diff --git a/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.cc b/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.cc
deleted file mode 100644
index 1003213..0000000
--- a/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.cc
+++ /dev/null
@@ -1,43 +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.
-
-#include "third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h"
-
-#include <memory>
-
-namespace blink {
-
-const RefCountedPropertyTreeState& RefCountedPropertyTreeState::Root() {
-  DEFINE_STATIC_LOCAL(
-      std::unique_ptr<RefCountedPropertyTreeState>, root,
-      (std::make_unique<RefCountedPropertyTreeState>(
-          TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
-          EffectPaintPropertyNode::Root())));
-  return *root;
-}
-
-const CompositorElementId RefCountedPropertyTreeState::GetCompositorElementId(
-    const CompositorElementIdSet& element_ids) const {
-  // The effect or transform nodes could have a compositor element id. The order
-  // doesn't matter as the element id should be the same on all that have a
-  // non-default CompositorElementId.
-  //
-  // Note that RefCountedPropertyTreeState acts as a context that accumulates
-  // state as we traverse the tree building layers. This means that we could see
-  // a compositor element id 'A' for a parent layer in conjunction with a
-  // compositor element id 'B' for a child layer. To preserve uniqueness of
-  // element ids, then, we check for presence in the |element_ids| set (which
-  // represents element ids already previously attached to a layer). This is an
-  // interim step while we pursue broader rework of animation subsystem noted in
-  // http://crbug.com/709137.
-  if (Effect()->GetCompositorElementId() &&
-      !element_ids.Contains(Effect()->GetCompositorElementId()))
-    return Effect()->GetCompositorElementId();
-  if (Transform()->GetCompositorElementId() &&
-      !element_ids.Contains(Transform()->GetCompositorElementId()))
-    return Transform()->GetCompositorElementId();
-  return CompositorElementId();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h b/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h
deleted file mode 100644
index 340d6e95..0000000
--- a/third_party/blink/renderer/platform/graphics/paint/ref_counted_property_tree_state.h
+++ /dev/null
@@ -1,88 +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.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_REF_COUNTED_PROPERTY_TREE_STATE_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_REF_COUNTED_PROPERTY_TREE_STATE_H_
-
-#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
-#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
-#include "third_party/blink/renderer/platform/wtf/hash_traits.h"
-#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
-
-namespace blink {
-
-// A complete set of paint properties including those that are inherited from
-// other objects.  RefPtrs are used to guard against use-after-free bugs.
-class PLATFORM_EXPORT RefCountedPropertyTreeState {
-  USING_FAST_MALLOC(RefCountedPropertyTreeState);
-
- public:
-  RefCountedPropertyTreeState(const TransformPaintPropertyNode* transform,
-                              const ClipPaintPropertyNode* clip,
-                              const EffectPaintPropertyNode* effect)
-      : transform_(transform), clip_(clip), effect_(effect) {}
-
-  RefCountedPropertyTreeState(const PropertyTreeState& property_tree_state)
-      : transform_(property_tree_state.Transform()),
-        clip_(property_tree_state.Clip()),
-        effect_(property_tree_state.Effect()) {}
-
-  bool HasDirectCompositingReasons() const;
-
-  const TransformPaintPropertyNode* Transform() const {
-    return transform_.get();
-  }
-  void SetTransform(scoped_refptr<const TransformPaintPropertyNode> node) {
-    transform_ = std::move(node);
-  }
-
-  const ClipPaintPropertyNode* Clip() const { return clip_.get(); }
-  void SetClip(scoped_refptr<const ClipPaintPropertyNode> node) {
-    clip_ = std::move(node);
-  }
-
-  const EffectPaintPropertyNode* Effect() const { return effect_.get(); }
-  void SetEffect(scoped_refptr<const EffectPaintPropertyNode> node) {
-    effect_ = std::move(node);
-  }
-
-  static const RefCountedPropertyTreeState& Root();
-
-  PropertyTreeState GetPropertyTreeState() const {
-    return PropertyTreeState(transform_.get(), clip_.get(), effect_.get());
-  }
-
-  // Returns the compositor element id, if any, for this property state. If
-  // neither the effect nor transform nodes have a compositor element id then a
-  // default instance is returned.
-  const CompositorElementId GetCompositorElementId(
-      const CompositorElementIdSet& element_ids) const;
-
-  void ClearChangedToRoot() const {
-    Transform()->ClearChangedToRoot();
-    Clip()->ClearChangedToRoot();
-    Effect()->ClearChangedToRoot();
-  }
-
-  String ToString() const { return GetPropertyTreeState().ToString(); }
-#if DCHECK_IS_ON()
-  // Dumps the tree from this state up to the root as a string.
-  String ToTreeString() const { return GetPropertyTreeState().ToTreeString(); }
-#endif
-
- private:
-  scoped_refptr<const TransformPaintPropertyNode> transform_;
-  scoped_refptr<const ClipPaintPropertyNode> clip_;
-  scoped_refptr<const EffectPaintPropertyNode> effect_;
-};
-
-inline bool operator==(const RefCountedPropertyTreeState& a,
-                       const RefCountedPropertyTreeState& b) {
-  return a.Transform() == b.Transform() && a.Clip() == b.Clip() &&
-         a.Effect() == b.Effect();
-}
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_REF_COUNTED_PROPERTY_TREE_STATE_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.cc
index 8426d1f..3420e6008 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.cc
@@ -13,13 +13,13 @@
 ScrollHitTestDisplayItem::ScrollHitTestDisplayItem(
     const DisplayItemClient& client,
     Type type,
-    scoped_refptr<const TransformPaintPropertyNode> scroll_offset_node)
+    const TransformPaintPropertyNode& scroll_offset_node)
     : DisplayItem(client, type, sizeof(*this)),
-      scroll_offset_node_(std::move(scroll_offset_node)) {
+      scroll_offset_node_(scroll_offset_node) {
   DCHECK(RuntimeEnabledFeatures::SlimmingPaintV2Enabled());
   DCHECK(IsScrollHitTestType(type));
   // The scroll offset transform node should have an associated scroll node.
-  DCHECK(scroll_offset_node_->ScrollNode());
+  DCHECK(scroll_offset_node_.ScrollNode());
 }
 
 ScrollHitTestDisplayItem::~ScrollHitTestDisplayItem() = default;
@@ -44,7 +44,7 @@
 void ScrollHitTestDisplayItem::PropertiesAsJSON(JSONObject& json) const {
   DisplayItem::PropertiesAsJSON(json);
   json.SetString("scrollOffsetNode",
-                 String::Format("%p", scroll_offset_node_.get()));
+                 String::Format("%p", &scroll_offset_node_));
 }
 #endif
 
@@ -52,19 +52,19 @@
     GraphicsContext& context,
     const DisplayItemClient& client,
     DisplayItem::Type type,
-    scoped_refptr<const TransformPaintPropertyNode> scroll_offset_node) {
+    const TransformPaintPropertyNode& scroll_offset_node) {
   PaintController& paint_controller = context.GetPaintController();
 
   // The scroll hit test should be in the non-scrolled transform space and
   // therefore should not be scrolled by the associated scroll offset.
   DCHECK_NE(paint_controller.CurrentPaintChunkProperties().Transform(),
-            scroll_offset_node.get());
+            &scroll_offset_node);
 
   if (paint_controller.DisplayItemConstructionIsDisabled())
     return;
 
   paint_controller.CreateAndAppend<ScrollHitTestDisplayItem>(
-      client, type, std::move(scroll_offset_node));
+      client, type, scroll_offset_node);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h b/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h
index fb9b5cd..ce22280 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h
+++ b/third_party/blink/renderer/platform/graphics/paint/scroll_hit_test_display_item.h
@@ -27,11 +27,11 @@
   ScrollHitTestDisplayItem(
       const DisplayItemClient&,
       Type,
-      scoped_refptr<const TransformPaintPropertyNode> scroll_offset_node);
+      const TransformPaintPropertyNode& scroll_offset_node);
   ~ScrollHitTestDisplayItem() override;
 
   const TransformPaintPropertyNode& scroll_offset_node() const {
-    return *scroll_offset_node_;
+    return scroll_offset_node_;
   }
 
   // DisplayItem
@@ -46,18 +46,17 @@
   // Create and append a ScrollHitTestDisplayItem onto the context. This is
   // similar to a recorder class (e.g., DrawingRecorder) but just emits a single
   // item.
-  static void Record(
-      GraphicsContext&,
-      const DisplayItemClient&,
-      DisplayItem::Type,
-      scoped_refptr<const TransformPaintPropertyNode> scroll_offset_node);
+  static void Record(GraphicsContext&,
+                     const DisplayItemClient&,
+                     DisplayItem::Type,
+                     const TransformPaintPropertyNode& scroll_offset_node);
 
  private:
   const ScrollPaintPropertyNode& scroll_node() const {
-    return *scroll_offset_node_->ScrollNode();
+    return *scroll_offset_node_.ScrollNode();
   }
 
-  scoped_refptr<const TransformPaintPropertyNode> scroll_offset_node_;
+  const TransformPaintPropertyNode& scroll_offset_node_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.cc
index 0329c33..b66fedb2 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.cc
@@ -6,9 +6,8 @@
 
 namespace blink {
 
-ScrollPaintPropertyNode* ScrollPaintPropertyNode::Root() {
-  DEFINE_STATIC_REF(ScrollPaintPropertyNode, root,
-                    (ScrollPaintPropertyNode::Create(nullptr, State{})));
+const ScrollPaintPropertyNode& ScrollPaintPropertyNode::Root() {
+  DEFINE_STATIC_LOCAL(ScrollPaintPropertyNode, root, (nullptr, State{}));
   return root;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
index 80bdd359..e1a27b6 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h
@@ -56,18 +56,17 @@
   };
 
   // This node is really a sentinel, and does not represent a real scroll.
-  static ScrollPaintPropertyNode* Root();
+  static const ScrollPaintPropertyNode& Root();
 
-  static scoped_refptr<ScrollPaintPropertyNode> Create(
-      scoped_refptr<const ScrollPaintPropertyNode> parent,
+  static std::unique_ptr<ScrollPaintPropertyNode> Create(
+      const ScrollPaintPropertyNode& parent,
       State&& state) {
-    return base::AdoptRef(
-        new ScrollPaintPropertyNode(std::move(parent), std::move(state)));
+    return base::WrapUnique(
+        new ScrollPaintPropertyNode(&parent, std::move(state)));
   }
 
-  bool Update(scoped_refptr<const ScrollPaintPropertyNode> parent,
-              State&& state) {
-    bool parent_changed = SetParent(parent);
+  bool Update(const ScrollPaintPropertyNode& parent, State&& state) {
+    bool parent_changed = SetParent(&parent);
     if (state == state_)
       return parent_changed;
 
@@ -118,8 +117,9 @@
 #if DCHECK_IS_ON()
   // The clone function is used by FindPropertiesNeedingUpdate.h for recording
   // a scroll node before it has been updated, to later detect changes.
-  scoped_refptr<ScrollPaintPropertyNode> Clone() const {
-    return base::AdoptRef(new ScrollPaintPropertyNode(Parent(), State(state_)));
+  std::unique_ptr<ScrollPaintPropertyNode> Clone() const {
+    return base::WrapUnique(
+        new ScrollPaintPropertyNode(Parent(), State(state_)));
   }
 
   // The equality operator is used by FindPropertiesNeedingUpdate.h for checking
@@ -132,9 +132,8 @@
   std::unique_ptr<JSONObject> ToJSON() const;
 
  private:
-  ScrollPaintPropertyNode(scoped_refptr<const ScrollPaintPropertyNode> parent,
-                          State&& state)
-      : PaintPropertyNode(std::move(parent)), state_(std::move(state)) {
+  ScrollPaintPropertyNode(const ScrollPaintPropertyNode* parent, State&& state)
+      : PaintPropertyNode(parent), state_(std::move(state)) {
     Validate();
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
index cc75a09f..4cdd6e5f 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
@@ -8,14 +8,13 @@
 
 // The root of the transform tree. The root transform node references the root
 // scroll node.
-TransformPaintPropertyNode* TransformPaintPropertyNode::Root() {
-  DEFINE_STATIC_REF(
+const TransformPaintPropertyNode& TransformPaintPropertyNode::Root() {
+  DEFINE_STATIC_LOCAL(
       TransformPaintPropertyNode, root,
-      base::AdoptRef(new TransformPaintPropertyNode(
-          nullptr,
-          State{TransformationMatrix(), FloatPoint3D(), false,
-                BackfaceVisibility::kVisible, 0, CompositingReason::kNone,
-                CompositorElementId(), ScrollPaintPropertyNode::Root()})));
+      (nullptr,
+       State{TransformationMatrix(), FloatPoint3D(), false,
+             BackfaceVisibility::kVisible, 0, CompositingReason::kNone,
+             CompositorElementId(), &ScrollPaintPropertyNode::Root()}));
   return root;
 }
 
@@ -61,7 +60,7 @@
                     state_.compositor_element_id.ToString().c_str());
   }
   if (state_.scroll)
-    json->SetString("scroll", String::Format("%p", state_.scroll.get()));
+    json->SetString("scroll", String::Format("%p", state_.scroll));
   return json;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index 9e33f59..4cd3696d 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -48,7 +48,7 @@
     unsigned rendering_context_id = 0;
     CompositingReasons direct_compositing_reasons = CompositingReason::kNone;
     CompositorElementId compositor_element_id;
-    scoped_refptr<const ScrollPaintPropertyNode> scroll;
+    const ScrollPaintPropertyNode* scroll = nullptr;
 
     bool operator==(const State& o) const {
       return matrix == o.matrix && origin == o.origin &&
@@ -63,18 +63,17 @@
 
   // This node is really a sentinel, and does not represent a real transform
   // space.
-  static TransformPaintPropertyNode* Root();
+  static const TransformPaintPropertyNode& Root();
 
-  static scoped_refptr<TransformPaintPropertyNode> Create(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
+  static std::unique_ptr<TransformPaintPropertyNode> Create(
+      const TransformPaintPropertyNode& parent,
       State&& state) {
-    return base::AdoptRef(
-        new TransformPaintPropertyNode(std::move(parent), std::move(state)));
+    return base::WrapUnique(
+        new TransformPaintPropertyNode(&parent, std::move(state)));
   }
 
-  bool Update(scoped_refptr<const TransformPaintPropertyNode> parent,
-              State&& state) {
-    bool parent_changed = SetParent(parent);
+  bool Update(const TransformPaintPropertyNode& parent, State&& state) {
+    bool parent_changed = SetParent(&parent);
     if (state == state_)
       return parent_changed;
 
@@ -88,9 +87,7 @@
   const FloatPoint3D& Origin() const { return state_.origin; }
 
   // The associated scroll node, or nullptr otherwise.
-  const ScrollPaintPropertyNode* ScrollNode() const {
-    return state_.scroll.get();
-  }
+  const ScrollPaintPropertyNode* ScrollNode() const { return state_.scroll; }
 
   // If this is a scroll offset translation (i.e., has an associated scroll
   // node), returns this. Otherwise, returns the transform node that this node
@@ -129,8 +126,8 @@
 #if DCHECK_IS_ON()
   // The clone function is used by FindPropertiesNeedingUpdate.h for recording
   // a transform node before it has been updated, to later detect changes.
-  scoped_refptr<TransformPaintPropertyNode> Clone() const {
-    return base::AdoptRef(
+  std::unique_ptr<TransformPaintPropertyNode> Clone() const {
+    return base::WrapUnique(
         new TransformPaintPropertyNode(Parent(), State(state_)));
   }
 
@@ -147,10 +144,9 @@
   size_t CacheMemoryUsageInBytes() const;
 
  private:
-  TransformPaintPropertyNode(
-      scoped_refptr<const TransformPaintPropertyNode> parent,
-      State&& state)
-      : PaintPropertyNode(std::move(parent)), state_(std::move(state)) {
+  TransformPaintPropertyNode(const TransformPaintPropertyNode* parent,
+                             State&& state)
+      : PaintPropertyNode(parent), state_(std::move(state)) {
     Validate();
   }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0f5780a..62beeb4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -105,7 +105,6 @@
     },
     {
       name: "AudioWorklet",
-      depends_on: ["Worklet"],
       status: "stable",
     },
     {
@@ -255,6 +254,7 @@
     },
     {
       name: "CSSEnvironmentVariables",
+      status: "experimental",
     },
     {
       name: "CSSFocusVisible",
@@ -309,13 +309,7 @@
       status: "stable",
     },
     {
-      name: "CSSPaintAPI",
-      depends_on: ["Worklet"],
-      status: "stable",
-    },
-    {
       name: "CSSPaintAPIArguments",
-      depends_on: ["CSSPaintAPI"],
       status: "experimental",
     },
     {
@@ -339,10 +333,6 @@
       status: "stable",
     },
     {
-      name: "CSSTypedOM",
-      status: "stable",
-    },
-    {
       name: "CSSVariables2",
       status: "experimental",
     },
@@ -1386,10 +1376,6 @@
       implied_by: ["WorkerNosniffBlock"],
     },
     {
-      name: "Worklet",
-      status: "stable",
-    },
-    {
       name: "WorkStealingInScriptRunner",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h b/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
index 14b2b28..945dbf1 100644
--- a/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
+++ b/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
@@ -12,10 +12,21 @@
 
 namespace blink {
 
-inline scoped_refptr<EffectPaintPropertyNode> CreateOpacityEffect(
-    scoped_refptr<const EffectPaintPropertyNode> parent,
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space,
-    scoped_refptr<const ClipPaintPropertyNode> output_clip,
+// Convenient shorthands.
+inline const TransformPaintPropertyNode& t0() {
+  return TransformPaintPropertyNode::Root();
+}
+inline const ClipPaintPropertyNode& c0() {
+  return ClipPaintPropertyNode::Root();
+}
+inline const EffectPaintPropertyNode& e0() {
+  return EffectPaintPropertyNode::Root();
+}
+
+inline std::unique_ptr<EffectPaintPropertyNode> CreateOpacityEffect(
+    const EffectPaintPropertyNode& parent,
+    const TransformPaintPropertyNode* local_transform_space,
+    const ClipPaintPropertyNode* output_clip,
     float opacity,
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
   EffectPaintPropertyNode::State state;
@@ -23,22 +34,21 @@
   state.output_clip = output_clip;
   state.opacity = opacity;
   state.direct_compositing_reasons = compositing_reasons;
-  return EffectPaintPropertyNode::Create(std::move(parent), std::move(state));
+  return EffectPaintPropertyNode::Create(parent, std::move(state));
 }
 
-inline scoped_refptr<EffectPaintPropertyNode> CreateOpacityEffect(
-    scoped_refptr<const EffectPaintPropertyNode> parent,
+inline std::unique_ptr<EffectPaintPropertyNode> CreateOpacityEffect(
+    const EffectPaintPropertyNode& parent,
     float opacity,
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
-  return CreateOpacityEffect(parent, parent->LocalTransformSpace(),
-                             parent->OutputClip(), opacity,
-                             compositing_reasons);
+  return CreateOpacityEffect(parent, parent.LocalTransformSpace(),
+                             parent.OutputClip(), opacity, compositing_reasons);
 }
 
-inline scoped_refptr<EffectPaintPropertyNode> CreateFilterEffect(
-    scoped_refptr<const EffectPaintPropertyNode> parent,
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space,
-    scoped_refptr<const ClipPaintPropertyNode> output_clip,
+inline std::unique_ptr<EffectPaintPropertyNode> CreateFilterEffect(
+    const EffectPaintPropertyNode& parent,
+    const TransformPaintPropertyNode* local_transform_space,
+    const ClipPaintPropertyNode* output_clip,
     CompositorFilterOperations filter,
     const FloatPoint& paint_offset = FloatPoint(),
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
@@ -48,22 +58,22 @@
   state.filter = std::move(filter);
   state.paint_offset = paint_offset;
   state.direct_compositing_reasons = compositing_reasons;
-  return EffectPaintPropertyNode::Create(std::move(parent), std::move(state));
+  return EffectPaintPropertyNode::Create(parent, std::move(state));
 }
 
-inline scoped_refptr<EffectPaintPropertyNode> CreateFilterEffect(
-    scoped_refptr<const EffectPaintPropertyNode> parent,
+inline std::unique_ptr<EffectPaintPropertyNode> CreateFilterEffect(
+    const EffectPaintPropertyNode& parent,
     CompositorFilterOperations filter,
     const FloatPoint& paint_offset = FloatPoint(),
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
-  return CreateFilterEffect(parent, parent->LocalTransformSpace(),
-                            parent->OutputClip(), filter, paint_offset,
+  return CreateFilterEffect(parent, parent.LocalTransformSpace(),
+                            parent.OutputClip(), filter, paint_offset,
                             compositing_reasons);
 }
 
-inline scoped_refptr<ClipPaintPropertyNode> CreateClip(
-    scoped_refptr<const ClipPaintPropertyNode> parent,
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space,
+inline std::unique_ptr<ClipPaintPropertyNode> CreateClip(
+    const ClipPaintPropertyNode& parent,
+    const TransformPaintPropertyNode* local_transform_space,
     const FloatRoundedRect& clip_rect,
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
   ClipPaintPropertyNode::State state;
@@ -73,9 +83,9 @@
   return ClipPaintPropertyNode::Create(parent, std::move(state));
 }
 
-inline scoped_refptr<ClipPaintPropertyNode> CreateClipPathClip(
-    scoped_refptr<const ClipPaintPropertyNode> parent,
-    scoped_refptr<const TransformPaintPropertyNode> local_transform_space,
+inline std::unique_ptr<ClipPaintPropertyNode> CreateClipPathClip(
+    const ClipPaintPropertyNode& parent,
+    const TransformPaintPropertyNode* local_transform_space,
     const FloatRoundedRect& clip_rect) {
   ClipPaintPropertyNode::State state;
   state.local_transform_space = local_transform_space;
@@ -84,8 +94,8 @@
   return ClipPaintPropertyNode::Create(parent, std::move(state));
 }
 
-inline scoped_refptr<TransformPaintPropertyNode> CreateTransform(
-    scoped_refptr<const TransformPaintPropertyNode> parent,
+inline std::unique_ptr<TransformPaintPropertyNode> CreateTransform(
+    const TransformPaintPropertyNode& parent,
     const TransformationMatrix& matrix,
     const FloatPoint3D& origin = FloatPoint3D(),
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
@@ -96,16 +106,16 @@
   return TransformPaintPropertyNode::Create(parent, std::move(state));
 }
 
-inline scoped_refptr<TransformPaintPropertyNode> CreateScrollTranslation(
-    scoped_refptr<const TransformPaintPropertyNode> parent,
+inline std::unique_ptr<TransformPaintPropertyNode> CreateScrollTranslation(
+    const TransformPaintPropertyNode& parent,
     float offset_x,
     float offset_y,
-    scoped_refptr<const ScrollPaintPropertyNode> scroll,
+    const ScrollPaintPropertyNode& scroll,
     CompositingReasons compositing_reasons = CompositingReason::kNone) {
   TransformPaintPropertyNode::State state;
   state.matrix.Translate(offset_x, offset_y);
   state.direct_compositing_reasons = compositing_reasons;
-  state.scroll = scroll;
+  state.scroll = &scroll;
   return TransformPaintPropertyNode::Create(parent, std::move(state));
 }
 
diff --git a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
index 0dfd644..85d12e9c 100644
--- a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
+++ b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
@@ -44,19 +44,18 @@
 TestPaintArtifact::~TestPaintArtifact() = default;
 
 TestPaintArtifact& TestPaintArtifact::Chunk(
-    scoped_refptr<const TransformPaintPropertyNode> transform,
-    scoped_refptr<const ClipPaintPropertyNode> clip,
-    scoped_refptr<const EffectPaintPropertyNode> effect) {
+    const TransformPaintPropertyNode& transform,
+    const ClipPaintPropertyNode& clip,
+    const EffectPaintPropertyNode& effect) {
   return Chunk(NewClient(), transform, clip, effect);
 }
 
 TestPaintArtifact& TestPaintArtifact::Chunk(
     DisplayItemClient& client,
-    scoped_refptr<const TransformPaintPropertyNode> transform,
-    scoped_refptr<const ClipPaintPropertyNode> clip,
-    scoped_refptr<const EffectPaintPropertyNode> effect) {
-  return Chunk(client,
-               PropertyTreeState(transform.get(), clip.get(), effect.get()));
+    const TransformPaintPropertyNode& transform,
+    const ClipPaintPropertyNode& clip,
+    const EffectPaintPropertyNode& effect) {
+  return Chunk(client, PropertyTreeState(&transform, &clip, &effect));
 }
 
 TestPaintArtifact& TestPaintArtifact::Chunk(
@@ -112,15 +111,15 @@
 }
 
 TestPaintArtifact& TestPaintArtifact::ScrollHitTest(
-    scoped_refptr<const TransformPaintPropertyNode> scroll_offset) {
+    const TransformPaintPropertyNode& scroll_offset) {
   return ScrollHitTest(NewClient(), scroll_offset);
 }
 
 TestPaintArtifact& TestPaintArtifact::ScrollHitTest(
     DisplayItemClient& client,
-    scoped_refptr<const TransformPaintPropertyNode> scroll_offset) {
+    const TransformPaintPropertyNode& scroll_offset) {
   display_item_list_.AllocateAndConstruct<ScrollHitTestDisplayItem>(
-      client, DisplayItem::kScrollHitTest, std::move(scroll_offset));
+      client, DisplayItem::kScrollHitTest, scroll_offset);
   return *this;
 }
 
diff --git a/third_party/blink/renderer/platform/testing/test_paint_artifact.h b/third_party/blink/renderer/platform/testing/test_paint_artifact.h
index 81aea7b..ec1f659 100644
--- a/third_party/blink/renderer/platform/testing/test_paint_artifact.h
+++ b/third_party/blink/renderer/platform/testing/test_paint_artifact.h
@@ -46,24 +46,24 @@
   ~TestPaintArtifact();
 
   // Add to the artifact.
-  TestPaintArtifact& Chunk(scoped_refptr<const TransformPaintPropertyNode>,
-                           scoped_refptr<const ClipPaintPropertyNode>,
-                           scoped_refptr<const EffectPaintPropertyNode>);
+  TestPaintArtifact& Chunk(const TransformPaintPropertyNode&,
+                           const ClipPaintPropertyNode&,
+                           const EffectPaintPropertyNode&);
   TestPaintArtifact& Chunk(const PropertyTreeState&);
   TestPaintArtifact& RectDrawing(const FloatRect& bounds, Color);
   TestPaintArtifact& ForeignLayer(const FloatPoint&,
                                   const IntSize&,
                                   scoped_refptr<cc::Layer>);
   TestPaintArtifact& ScrollHitTest(
-      scoped_refptr<const TransformPaintPropertyNode> scroll_offset);
+      const TransformPaintPropertyNode& scroll_offset);
   TestPaintArtifact& KnownToBeOpaque();
 
   // Add to the artifact, with specified display item client. These are used
   // to test incremental paint artifact updates.
   TestPaintArtifact& Chunk(DisplayItemClient&,
-                           scoped_refptr<const TransformPaintPropertyNode>,
-                           scoped_refptr<const ClipPaintPropertyNode>,
-                           scoped_refptr<const EffectPaintPropertyNode>);
+                           const TransformPaintPropertyNode&,
+                           const ClipPaintPropertyNode&,
+                           const EffectPaintPropertyNode&);
   TestPaintArtifact& Chunk(DisplayItemClient&, const PropertyTreeState&);
   TestPaintArtifact& RectDrawing(DisplayItemClient&,
                                  const FloatRect& bounds,
@@ -74,7 +74,7 @@
                                   scoped_refptr<cc::Layer>);
   TestPaintArtifact& ScrollHitTest(
       DisplayItemClient&,
-      scoped_refptr<const TransformPaintPropertyNode> scroll_offset);
+      const TransformPaintPropertyNode& scroll_offset);
 
   // Can't add more things once this is called.
   const PaintArtifact& Build();
diff --git a/third_party/fuchsia-sdk/BUILD.gn b/third_party/fuchsia-sdk/BUILD.gn
index 2ad624f..0094217 100644
--- a/third_party/fuchsia-sdk/BUILD.gn
+++ b/third_party/fuchsia-sdk/BUILD.gn
@@ -250,6 +250,9 @@
 }
 
 fuchsia_sdk_fidl_pkg("netstack") {
+  namespace = "fuchsia"
+  namespace_path = "fuchsia"
+
   sources = [
     "net_address.fidl",
     "netstack.fidl",
diff --git a/third_party/googletest/BUILD.gn b/third_party/googletest/BUILD.gn
index 33e8307..4b87207 100644
--- a/third_party/googletest/BUILD.gn
+++ b/third_party/googletest/BUILD.gn
@@ -97,6 +97,7 @@
   configs += [ "//build/config/compiler:no_chromium_code" ]
 
   defines = []
+  deps = []
   if (is_nacl || !build_with_chromium) {
     defines += [ "GTEST_DISABLE_PRINT_STACK_TRACE" ]
     sources -= [
@@ -104,15 +105,14 @@
       "custom/gtest/internal/custom/stack_trace_getter.h",
     ]
   } else {
-    deps = [
-      "//base",
+    deps += [ "//base" ]
+  }
+
+  if (is_fuchsia) {
+    deps += [
+      "//third_party/fuchsia-sdk:fdio",
+      "//third_party/fuchsia-sdk:launchpad",
     ]
-    if (is_fuchsia) {
-      deps += [
-        "//third_party/fuchsia-sdk:fdio",
-        "//third_party/fuchsia-sdk:launchpad",
-      ]
-    }
   }
 }
 
diff --git a/tools/binary_size/libsupersize/archive.py b/tools/binary_size/libsupersize/archive.py
index f9fb4b1..24cb7d3 100644
--- a/tools/binary_size/libsupersize/archive.py
+++ b/tools/binary_size/libsupersize/archive.py
@@ -606,7 +606,8 @@
   return size_info
 
 
-def CreateMetadata(map_path, elf_path, apk_path, tool_prefix, output_directory):
+def CreateMetadata(map_path, elf_path, apk_path, tool_prefix, output_directory,
+                   linker_name):
   metadata = None
   if elf_path:
     logging.debug('Constructing metadata')
@@ -623,6 +624,7 @@
         models.METADATA_ELF_ARCHITECTURE: architecture,
         models.METADATA_ELF_MTIME: timestamp,
         models.METADATA_ELF_BUILD_ID: build_id,
+        models.METADATA_LINKER_NAME: linker_name,
         models.METADATA_TOOL_PREFIX: relative_tool_prefix,
     }
 
@@ -1337,18 +1339,18 @@
   if not args.no_source_paths:
     output_directory = output_directory_finder.Finalized()
   return (output_directory, tool_prefix, apk_path, apk_so_path, elf_path,
-          map_path)
+          map_path, linker_name)
 
 
 def Run(args, parser):
   if not args.size_file.endswith('.size'):
     parser.error('size_file must end with .size')
 
-  (output_directory, tool_prefix, apk_path, apk_so_path, elf_path, map_path) = (
-      DeduceMainPaths(args, parser))
+  (output_directory, tool_prefix, apk_path, apk_so_path, elf_path, map_path,
+       linker_name) = (DeduceMainPaths(args, parser))
 
   metadata = CreateMetadata(map_path, elf_path, apk_path, tool_prefix,
-                            output_directory)
+                            output_directory, linker_name)
 
   section_sizes, raw_symbols = CreateSectionSizesAndSymbols(
       map_path=map_path, tool_prefix=tool_prefix, elf_path=elf_path,
diff --git a/tools/binary_size/libsupersize/console.py b/tools/binary_size/libsupersize/console.py
index c6756e5..b4dc755d 100644
--- a/tools/binary_size/libsupersize/console.py
+++ b/tools/binary_size/libsupersize/console.py
@@ -304,9 +304,11 @@
         size_info, tool_prefix, elf_path)
 
     args = [path_util.GetObjDumpPath(tool_prefix), '--disassemble', '--source',
-            '--line-numbers', '--demangle',
-            '--start-address=0x%x' % symbol.address,
+            '--line-numbers', '--start-address=0x%x' % symbol.address,
             '--stop-address=0x%x' % symbol.end_address, elf_path]
+    # llvm-objdump does not support '--demangle' switch.
+    if not self._tool_prefix_finder.IsLld():
+      args.append('--demangle')
     if self._disassemble_prefix_len is None:
       prefix_len = self._DetectDisassemblePrefixLen(args)
       if prefix_len is not None:
@@ -469,9 +471,11 @@
   output_directory_finder = path_util.OutputDirectoryFinder(
       value=args.output_directory,
       any_path_within_output_directory=args.inputs[0])
+  linker_name = size_infos[-1].metadata.get(models.METADATA_LINKER_NAME)
   tool_prefix_finder = path_util.ToolPrefixFinder(
       value=args.tool_prefix,
-      output_directory_finder=output_directory_finder)
+      output_directory_finder=output_directory_finder,
+      linker_name=linker_name)
   session = _Session(size_infos, output_directory_finder, tool_prefix_finder)
 
   if args.query:
diff --git a/tools/binary_size/libsupersize/integration_test.py b/tools/binary_size/libsupersize/integration_test.py
index 73cbcde..d1b0f13 100755
--- a/tools/binary_size/libsupersize/integration_test.py
+++ b/tools/binary_size/libsupersize/integration_test.py
@@ -185,7 +185,7 @@
         with _AddMocksToPath():
           metadata = archive.CreateMetadata(
               _TEST_MAP_PATH, elf_path, apk_path, _TEST_TOOL_PREFIX,
-              output_directory)
+              output_directory, 'gold')
       section_sizes, raw_symbols = archive.CreateSectionSizesAndSymbols(
           map_path=_TEST_MAP_PATH, tool_prefix=_TEST_TOOL_PREFIX,
           elf_path=elf_path, output_directory=output_directory,
diff --git a/tools/binary_size/libsupersize/models.py b/tools/binary_size/libsupersize/models.py
index 2293da83..9ed7dac 100644
--- a/tools/binary_size/libsupersize/models.py
+++ b/tools/binary_size/libsupersize/models.py
@@ -43,6 +43,7 @@
 METADATA_ELF_MTIME = 'elf_mtime'  # int timestamp in utc.
 METADATA_ELF_BUILD_ID = 'elf_build_id'
 METADATA_GN_ARGS = 'gn_args'
+METADATA_LINKER_NAME = 'linker_name'
 METADATA_TOOL_PREFIX = 'tool_prefix'  # Path relative to SRC_ROOT.
 
 SECTION_BSS = '.bss'
diff --git a/tools/binary_size/libsupersize/path_util.py b/tools/binary_size/libsupersize/path_util.py
index 1432fd3..051f482e 100644
--- a/tools/binary_size/libsupersize/path_util.py
+++ b/tools/binary_size/libsupersize/path_util.py
@@ -85,11 +85,14 @@
     self._output_directory_finder = output_directory_finder
     self._linker_name = linker_name;
 
+  def IsLld(self):
+    return self._linker_name.startswith('lld') if self._linker_name else True
+
   def Detect(self):
     output_directory = self._output_directory_finder.Tentative()
     if output_directory:
       ret = None
-      if self._linker_name.startswith('lld'):
+      if self.IsLld():
         ret = os.path.join(SRC_ROOT, 'third_party', 'llvm-build',
                            'Release+Asserts', 'bin', 'llvm-')
       else:
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Apk.golden b/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
index e7c77621..1ddc72d0 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
@@ -6,6 +6,7 @@
 elf_mtime={redacted}
 git_revision=abc123
 gn_args=var1=true var2="foo"
+linker_name=gold
 map_file_name=../test.map
 tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 Section .text: has 100.0% of 35900712 bytes accounted for from 19 symbols. 0 bytes are unaccounted for.
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Elf.golden b/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
index 9ecd1290..b786502 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
@@ -4,6 +4,7 @@
 elf_mtime={redacted}
 git_revision=abc123
 gn_args=var1=true var2="foo"
+linker_name=gold
 map_file_name=../test.map
 tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 Section .text: has 100.0% of 35900712 bytes accounted for from 19 symbols. 0 bytes are unaccounted for.
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden b/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
index df5f8590..03a6b23 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
@@ -4,6 +4,7 @@
 elf_mtime={redacted}
 git_revision=abc123
 gn_args=var1=true var2="foo"
+linker_name=gold
 map_file_name=../test.map
 tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 Section .text: has 100.0% of 35900712 bytes accounted for from 19 symbols. 0 bytes are unaccounted for.
diff --git a/tools/binary_size/libsupersize/testdata/Console.golden b/tools/binary_size/libsupersize/testdata/Console.golden
index 621ef5d7..3d5aecfa 100644
--- a/tools/binary_size/libsupersize/testdata/Console.golden
+++ b/tools/binary_size/libsupersize/testdata/Console.golden
@@ -65,6 +65,7 @@
     elf_mtime={redacted}
     git_revision=abc123
     gn_args=var1=true var2="foo"
+    linker_name=gold
     map_file_name=../test.map
     tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 
diff --git a/tools/binary_size/libsupersize/testdata/Diff_NullDiff.golden b/tools/binary_size/libsupersize/testdata/Diff_NullDiff.golden
index 3f77d804..0f3515a 100644
--- a/tools/binary_size/libsupersize/testdata/Diff_NullDiff.golden
+++ b/tools/binary_size/libsupersize/testdata/Diff_NullDiff.golden
@@ -5,6 +5,7 @@
     elf_mtime={redacted}
     git_revision=abc123
     gn_args=var1=true var2="foo"
+    linker_name=gold
     map_file_name=../test.map
     tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 Old Metadata:
diff --git a/tools/binary_size/libsupersize/testdata/FullDescription.golden b/tools/binary_size/libsupersize/testdata/FullDescription.golden
index 5bef11d..f24233f 100644
--- a/tools/binary_size/libsupersize/testdata/FullDescription.golden
+++ b/tools/binary_size/libsupersize/testdata/FullDescription.golden
@@ -5,6 +5,7 @@
     elf_mtime={redacted}
     git_revision=abc123
     gn_args=var1=true var2="foo"
+    linker_name=gold
     map_file_name=../test.map
     tool_prefix=tools/binary_size/libsupersize/testdata/mock_toolchain/
 
diff --git a/tools/luci-go/linux64/isolate.sha1 b/tools/luci-go/linux64/isolate.sha1
index 5a23e1c..1610657 100644
--- a/tools/luci-go/linux64/isolate.sha1
+++ b/tools/luci-go/linux64/isolate.sha1
@@ -1 +1 @@
-b1c3c39fe8fe084bd2ec4ed0f03037751a3a8650
+9734e966a14f9e26f86e38a020fcd7584248d285
diff --git a/tools/luci-go/mac64/isolate.sha1 b/tools/luci-go/mac64/isolate.sha1
index 72d9784..a61e43ab 100644
--- a/tools/luci-go/mac64/isolate.sha1
+++ b/tools/luci-go/mac64/isolate.sha1
@@ -1 +1 @@
-ffb6a624bd14abdff34618fe97562b34350199f7
+18561de57e944d096521838b4e6cb49e0cc1df23
diff --git a/tools/luci-go/win64/isolate.exe.sha1 b/tools/luci-go/win64/isolate.exe.sha1
index d3ab3b3..5ac52a0 100644
--- a/tools/luci-go/win64/isolate.exe.sha1
+++ b/tools/luci-go/win64/isolate.exe.sha1
@@ -1 +1 @@
-bb2a587cbc0a8e5b0ae41be4ffb5ad33b213dcf9
+af227603890ea1d8c082b5caf15e46a6bf060a2e
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 0932e6cc..4b3c1cf3 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -11104,6 +11104,15 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="MediaContextMenu_EnterPictureInPicture">
+  <owner>apacible@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <description>
+    User clicked on the contextual menu of a video player to enter
+    Picture-in-Picture mode.
+  </description>
+</action>
+
 <action name="MediaContextMenu_Loop">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 2e6d21f..2667b76 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -33242,12 +33242,36 @@
   <int value="4" label="UNREGISTRATION_FAILED"/>
 </enum>
 
-<enum name="NotificationHelperNotificationActivatorStatus">
+<enum name="NotificationHelperNotificationActivatorPrimaryStatus">
   <int value="0" label="Success"/>
   <int value="1" label="Chrome exe missing"/>
-  <int value="2" label="Launch chrome failed"/>
+  <int value="2" label="ShellExecute failed"/>
+</enum>
+
+<enum name="NotificationHelperNotificationActivatorSecondaryStatus">
+  <int value="0" label="Success"/>
+  <int value="1" label="LaunchId empty"/>
+  <int value="2" label="AllowSetForegroundWindow failed"/>
+  <int value="3" label="LaunchId empty, AllowSetForegroundWindow failed"/>
+  <int value="4" label="Handle missing"/>
+  <int value="5" label="LaunchId empty, Handle missing"/>
+  <int value="6" label="AllowSetForegroundWindow failed, Handle missing"/>
+  <int value="7"
+      label="LaunchId empty, AllowSetForegroundWindow failed, Handle missing"/>
+</enum>
+
+<enum name="NotificationHelperNotificationActivatorStatus">
+  <obsolete>
+    Deprecated as of 06/2018. Replaced by
+    NotificationHelperNotificationActivatorPrimaryStatus and
+    NotificationHelperNotificationActivatorSecondaryStatus.
+  </obsolete>
+  <int value="0" label="Success"/>
+  <int value="1" label="Chrome exe missing"/>
+  <int value="2" label="ShellExecute failed"/>
   <int value="3" label="Launch id empty"/>
   <int value="4" label="AllowSetForegroundWindow call failed"/>
+  <int value="5" label="Chrome process launch failed"/>
 </enum>
 
 <enum name="NotifierType">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ec31a11..1bd1420a 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -59036,8 +59036,34 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Notifications.NotificationHelper.NotificationActivatorPrimaryStatus"
+    enum="NotificationHelperNotificationActivatorPrimaryStatus">
+  <owner>chengx@chromium.org</owner>
+  <owner>finnur@chromium.org</owner>
+  <summary>
+    The primary execute status of NotificationActivator::Activate. Logged
+    whenever a notification_helper process is launched by Windows.
+  </summary>
+</histogram>
+
+<histogram
+    name="Notifications.NotificationHelper.NotificationActivatorSecondaryStatus"
+    enum="NotificationHelperNotificationActivatorSecondaryStatus">
+  <owner>chengx@chromium.org</owner>
+  <owner>finnur@chromium.org</owner>
+  <summary>
+    The secondary execute status of NotificationActivator::Activate. Logged
+    whenever a notification_helper process is launched by Windows.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.NotificationHelper.NotificationActivatorStatus"
     enum="NotificationHelperNotificationActivatorStatus">
+  <obsolete>
+    Deprecated 06/2018. Replaced by NotificationActivatorPrimaryStatus and
+    NotificationActivatorSecondaryStatus.
+  </obsolete>
   <owner>chengx@chromium.org</owner>
   <summary>
     The execute status of NotificationActivator::Activate. Logged whenever a
@@ -94526,6 +94552,10 @@
 <histogram
     name="SubresourceFilter.PageLoad.SafeBrowsingDelay.NoRedirectSpeculation"
     units="ms">
+  <obsolete>
+    Deprecated in May 2018 (M69). We have enough data to show that the redirect
+    speculations we do are necessary, especially for Android.
+  </obsolete>
   <owner>csharrison@chromium.org</owner>
   <summary>
     The navigation delay that would have been imposed by the subresource filter
diff --git a/tools/perf/page_sets/webgl_supported_shared_state.py b/tools/perf/page_sets/webgl_supported_shared_state.py
index f2f7e7b7..ad9b04fe 100644
--- a/tools/perf/page_sets/webgl_supported_shared_state.py
+++ b/tools/perf/page_sets/webgl_supported_shared_state.py
@@ -16,8 +16,9 @@
     # Check the skipped GPUs list.
     # Requires the page provide a "skipped_gpus" property.
     browser = browser_info.browser
-    if browser.supports_system_info:
-      gpu_info = browser.GetSystemInfo().gpu
+    system_info = browser.GetSystemInfo()
+    if system_info:
+      gpu_info = system_info.gpu
       gpu_vendor = self._GetGpuVendorString(gpu_info)
       if gpu_vendor in page.skipped_gpus:
         return False
diff --git a/ui/accelerated_widget_mac/BUILD.gn b/ui/accelerated_widget_mac/BUILD.gn
index 83736977..5d4f127 100644
--- a/ui/accelerated_widget_mac/BUILD.gn
+++ b/ui/accelerated_widget_mac/BUILD.gn
@@ -16,6 +16,8 @@
     "ca_layer_tree_coordinator.mm",
     "ca_renderer_layer_tree.h",
     "ca_renderer_layer_tree.mm",
+    "ca_transaction_observer.h",
+    "ca_transaction_observer.mm",
     "display_ca_layer_tree.h",
     "display_ca_layer_tree.mm",
     "display_link_mac.cc",
diff --git a/ui/accelerated_widget_mac/ca_transaction_observer.h b/ui/accelerated_widget_mac/ca_transaction_observer.h
new file mode 100644
index 0000000..d3e0a2dd
--- /dev/null
+++ b/ui/accelerated_widget_mac/ca_transaction_observer.h
@@ -0,0 +1,61 @@
+// 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.
+
+#ifndef UI_ACCELERATED_WIDGET_MAC_CA_TRANSACTION_OBSERVER_H_
+#define UI_ACCELERATED_WIDGET_MAC_CA_TRANSACTION_OBSERVER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+
+#include "ui/accelerated_widget_mac/accelerated_widget_mac_export.h"
+
+namespace base {
+template <typename T>
+class NoDestructor;
+}  // namespace base
+
+namespace ui {
+
+class ACCELERATED_WIDGET_MAC_EXPORT CATransactionCoordinator {
+ public:
+  class PreCommitObserver {
+   public:
+    virtual bool ShouldWaitInPreCommit() = 0;
+    virtual base::TimeDelta PreCommitTimeout() = 0;
+  };
+
+  class PostCommitObserver {
+   public:
+    virtual void OnActivateForTransaction() = 0;
+    virtual void OnEnterPostCommit() = 0;
+    virtual bool ShouldWaitInPostCommit() = 0;
+  };
+
+  static CATransactionCoordinator& Get();
+
+  void Synchronize();
+
+  void AddPreCommitObserver(PreCommitObserver*);
+  void RemovePreCommitObserver(PreCommitObserver*);
+
+  void AddPostCommitObserver(PostCommitObserver*);
+  void RemovePostCommitObserver(PostCommitObserver*);
+
+ private:
+  friend class base::NoDestructor<CATransactionCoordinator>;
+  CATransactionCoordinator();
+  ~CATransactionCoordinator();
+
+  bool active_ = false;
+  base::ObserverList<PreCommitObserver> pre_commit_observers_;
+  base::ObserverList<PostCommitObserver> post_commit_observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(CATransactionCoordinator);
+};
+
+}  // namespace ui
+
+#endif  // UI_ACCELERATED_WIDGET_MAC_CA_TRANSACTION_OBSERVER_H_
diff --git a/ui/accelerated_widget_mac/ca_transaction_observer.mm b/ui/accelerated_widget_mac/ca_transaction_observer.mm
new file mode 100644
index 0000000..d48ce87
--- /dev/null
+++ b/ui/accelerated_widget_mac/ca_transaction_observer.mm
@@ -0,0 +1,109 @@
+// 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.
+
+#include "ui/accelerated_widget_mac/ca_transaction_observer.h"
+
+#include "base/no_destructor.h"
+#include "base/trace_event/trace_event.h"
+
+#import <AppKit/AppKit.h>
+#import <QuartzCore/QuartzCore.h>
+
+typedef enum {
+  kCATransactionPhasePreLayout,
+  kCATransactionPhasePreCommit,
+  kCATransactionPhasePostCommit,
+} CATransactionPhase;
+
+@interface CATransaction ()
++ (void)addCommitHandler:(void (^)(void))block
+                forPhase:(CATransactionPhase)phase;
+@end
+
+namespace ui {
+
+namespace {
+constexpr auto kPostCommitTimeout = base::TimeDelta::FromMilliseconds(50);
+}  // namespace
+
+CATransactionCoordinator& CATransactionCoordinator::Get() {
+  static base::NoDestructor<CATransactionCoordinator> instance;
+  return *instance;
+}
+
+void CATransactionCoordinator::Synchronize() {
+  if (active_)
+    return;
+  active_ = true;
+
+  for (auto& observer : post_commit_observers_)
+    observer.OnActivateForTransaction();
+
+  [CATransaction addCommitHandler:^{
+    TRACE_EVENT0("ui", "CATransactionCoordinator: pre-commit handler");
+
+    NSDate* start_date = [NSDate date];
+    for (;;) {
+      base::TimeDelta timeout;
+      for (auto& observer : pre_commit_observers_) {
+        if (observer.ShouldWaitInPreCommit())
+          timeout = std::max(timeout, observer.PreCommitTimeout());
+      }
+      NSDate* deadline =
+          [start_date dateByAddingTimeInterval:timeout.InSecondsF()];
+      if ([deadline isLessThanOrEqualTo:[NSDate date]])
+        break;
+      [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode
+                             beforeDate:deadline];
+    }
+  }
+                         forPhase:kCATransactionPhasePreCommit];
+
+  [CATransaction addCommitHandler:^{
+    TRACE_EVENT0("ui", "CATransactionCoordinator: post-commit handler");
+
+    for (auto& observer : post_commit_observers_)
+      observer.OnEnterPostCommit();
+
+    NSDate* deadline =
+        [NSDate dateWithTimeIntervalSinceNow:kPostCommitTimeout.InSecondsF()];
+    for (;;) {
+      if (!std::any_of(
+              post_commit_observers_.begin(), post_commit_observers_.end(),
+              std::mem_fn(&PostCommitObserver::ShouldWaitInPostCommit)))
+        break;
+      if ([deadline isLessThanOrEqualTo:[NSDate date]])
+        break;
+      [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode
+                             beforeDate:deadline];
+    }
+    active_ = false;
+  }
+                         forPhase:kCATransactionPhasePostCommit];
+}
+
+CATransactionCoordinator::CATransactionCoordinator() = default;
+CATransactionCoordinator::~CATransactionCoordinator() = default;
+
+void CATransactionCoordinator::AddPreCommitObserver(
+    PreCommitObserver* observer) {
+  pre_commit_observers_.AddObserver(observer);
+}
+
+void CATransactionCoordinator::RemovePreCommitObserver(
+    PreCommitObserver* observer) {
+  pre_commit_observers_.RemoveObserver(observer);
+}
+
+void CATransactionCoordinator::AddPostCommitObserver(
+    PostCommitObserver* observer) {
+  post_commit_observers_.AddObserver(observer);
+}
+
+void CATransactionCoordinator::RemovePostCommitObserver(
+    PostCommitObserver* observer) {
+  post_commit_observers_.RemoveObserver(observer);
+}
+
+}  // namespace ui
diff --git a/ui/android/view_android.h b/ui/android/view_android.h
index fb4ddce5..c723346 100644
--- a/ui/android/view_android.h
+++ b/ui/android/view_android.h
@@ -211,7 +211,6 @@
 
   void SetLayoutForTesting(int x, int y, int width, int height);
 
-  // TODO(jinsukkim): Use OnceCallback, as it is used at most once.
   template <typename E>
   using EventHandlerCallback =
       const base::RepeatingCallback<bool(EventHandlerAndroid*, const E&)>;
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index faaf22a..cc8f190 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -207,10 +207,14 @@
   }
 
   if (use_ozone) {
-    deps += [ "//ui/ozone" ]
+    deps += [
+      "//ui/events/ozone:events_ozone_layout",
+      "//ui/ozone",
+    ]
     sources += [
       "mus/platform_event_source_mus_ozone.cc",
       "mus/platform_event_source_mus_ozone.h",
+      "window_tree_host_platform_ozone.cc",
     ]
   }
 
diff --git a/ui/aura/window_event_dispatcher.cc b/ui/aura/window_event_dispatcher.cc
index 2ddd91a..5e360a26 100644
--- a/ui/aura/window_event_dispatcher.cc
+++ b/ui/aura/window_event_dispatcher.cc
@@ -103,9 +103,7 @@
     : host_(host),
       are_events_in_pixels_(are_events_in_pixels),
       observer_manager_(this),
-      event_targeter_(std::make_unique<WindowTargeter>()),
-      repost_event_factory_(this),
-      held_event_factory_(this) {
+      event_targeter_(std::make_unique<WindowTargeter>()) {
   ui::GestureRecognizer::Get()->AddGestureEventHelper(this);
   Env::GetInstance()->AddObserver(this);
   if (Env::GetInstance()->mode() == Env::Mode::MUS)
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc
index d652a39..dad9b28 100644
--- a/ui/aura/window_tree_host_platform.cc
+++ b/ui/aura/window_tree_host_platform.cc
@@ -152,11 +152,13 @@
   return keyboard_hook_ && keyboard_hook_->IsKeyLocked(dom_code);
 }
 
+#if !defined(USE_OZONE)
 base::flat_map<std::string, std::string>
 WindowTreeHostPlatform::GetKeyboardLayoutMap() {
   NOTIMPLEMENTED();
   return {};
 }
+#endif
 
 void WindowTreeHostPlatform::SetCursorNative(gfx::NativeCursor cursor) {
   if (cursor == current_cursor_)
diff --git a/ui/aura/window_tree_host_platform_ozone.cc b/ui/aura/window_tree_host_platform_ozone.cc
new file mode 100644
index 0000000..b08bf4a
--- /dev/null
+++ b/ui/aura/window_tree_host_platform_ozone.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#include "ui/aura/window_tree_host_platform.h"
+
+#include "ui/events/keycodes/dom/dom_code.h"
+#include "ui/events/keycodes/dom/dom_key.h"
+#include "ui/events/keycodes/dom/dom_keyboard_layout_manager.h"
+#include "ui/events/ozone/layout/keyboard_layout_engine.h"
+#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
+
+namespace aura {
+
+base::flat_map<std::string, std::string>
+WindowTreeHostPlatform::GetKeyboardLayoutMap() {
+  std::unique_ptr<ui::DomKeyboardLayoutManager> layouts =
+      std::make_unique<ui::DomKeyboardLayoutManager>();
+
+  ui::KeyboardLayoutEngine* kle =
+      ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine();
+
+  for (unsigned int i_domcode = 0;
+       i_domcode < ui::kWritingSystemKeyDomCodeEntries; ++i_domcode) {
+    ui::DomCode dom_code = ui::writing_system_key_domcodes[i_domcode];
+    ui::DomKey dom_key;
+    ui::KeyboardCode code;
+    if (kle->Lookup(dom_code, 0, &dom_key, &code)) {
+      int32_t unicode = 0;
+      if (dom_key.IsDeadKey())
+        unicode = dom_key.ToDeadKeyCombiningCharacter();
+      else if (dom_key.IsCharacter())
+        unicode = dom_key.ToCharacter();
+      if (unicode)
+        // There is only 1 layout available on Ozone, so we arbitrarily assign
+        // it to be id = 0.
+        layouts->GetLayout(0)->AddKeyMapping(dom_code, unicode);
+    }
+  }
+  return layouts->GetFirstAsciiCapableLayout()->GetMap();
+}
+
+}  // namespace aura
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index fbda0b1..69fffbe 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -381,10 +381,13 @@
     case KeyboardControllerState::SHOWN: {
       SetTouchEventLogging(true /* enable */);
 
-      keyboard::LogKeyboardControlEvent(
-          reason == HIDE_REASON_AUTOMATIC
-              ? keyboard::KEYBOARD_CONTROL_HIDE_AUTO
-              : keyboard::KEYBOARD_CONTROL_HIDE_USER);
+      if (reason == HIDE_REASON_AUTOMATIC) {
+        keyboard::LogKeyboardControlEvent(keyboard::KEYBOARD_CONTROL_HIDE_AUTO);
+        time_of_last_blur_ = base::Time::Now();
+      } else {
+        keyboard::LogKeyboardControlEvent(keyboard::KEYBOARD_CONTROL_HIDE_USER);
+        time_of_last_blur_ = base::Time::UnixEpoch();
+      }
 
       NotifyContentsBoundsChanging(gfx::Rect());
 
@@ -742,8 +745,6 @@
   if (state_ == state)
     return;
 
-  KeyboardControllerState original_state = state_;
-
   state_ = state;
 
   if (state != KeyboardControllerState::WILL_HIDE)
@@ -757,16 +758,11 @@
   switch (state_) {
     case KeyboardControllerState::LOADING_EXTENSION:
     case KeyboardControllerState::WILL_HIDE:
-      if (state_ == KeyboardControllerState::WILL_HIDE &&
-          original_state == KeyboardControllerState::SHOWN) {
-        time_of_last_blur_ = base::Time::Now();
-      }
       base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
           FROM_HERE,
           base::BindOnce(&KeyboardController::ReportLingeringState,
                          weak_factory_report_lingering_state_.GetWeakPtr()),
           base::TimeDelta::FromMilliseconds(kReportLingeringStateDelayMs));
-
       break;
     default:
       // Do nothing
@@ -855,7 +851,7 @@
 }
 
 void KeyboardController::DismissVirtualKeyboard() {
-  HideKeyboard(HIDE_REASON_AUTOMATIC);
+  HideKeyboard(HIDE_REASON_MANUAL);
 }
 
 void KeyboardController::AddObserver(
diff --git a/ui/views/cocoa/bridged_native_widget.h b/ui/views/cocoa/bridged_native_widget.h
index ec83dfb3..83af13a 100644
--- a/ui/views/cocoa/bridged_native_widget.h
+++ b/ui/views/cocoa/bridged_native_widget.h
@@ -14,6 +14,7 @@
 #include "base/macros.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #import "ui/accelerated_widget_mac/accelerated_widget_mac.h"
+#include "ui/accelerated_widget_mac/ca_transaction_observer.h"
 #include "ui/accelerated_widget_mac/display_ca_layer_tree.h"
 #include "ui/base/ime/input_method_delegate.h"
 #include "ui/compositor/layer_owner.h"
@@ -47,7 +48,8 @@
 // DesktopNativeWidgetMac. Serves as a helper class to bridge requests from the
 // NativeWidgetMac to the Cocoa window. Behaves a bit like an aura::Window.
 class VIEWS_EXPORT BridgedNativeWidget
-    : public ui::LayerDelegate,
+    : public ui::CATransactionCoordinator::PreCommitObserver,
+      public ui::LayerDelegate,
       public ui::LayerOwner,
       public ui::internal::InputMethodDelegate,
       public CocoaMouseCaptureDelegate,
@@ -212,6 +214,10 @@
   bool animate() const { return animate_; }
   void set_animate(bool animate) { animate_ = animate; }
 
+  // ui::CATransactionCoordinator::PreCommitObserver implementation
+  bool ShouldWaitInPreCommit() override;
+  base::TimeDelta PreCommitTimeout() override;
+
   // Overridden from ui::internal::InputMethodDelegate:
   ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override;
 
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index c37fd62..0087fad 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -51,6 +51,9 @@
                                          int radius);
 
 }
+namespace {
+constexpr auto kUIPaintTimeout = base::TimeDelta::FromMilliseconds(250);
+}  // namespace
 
 // The NSView that hosts the composited CALayer drawing the UI. It fills the
 // window but is not hittable so that accessibility hit tests always go to the
@@ -251,6 +254,7 @@
   DCHECK(parent);
   window_delegate_.reset(
       [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
+  ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
 }
 
 BridgedNativeWidget::~BridgedNativeWidget() {
@@ -259,6 +263,7 @@
   // destructor is called.
   DCHECK(![window_ delegate]);
 
+  ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this);
   RemoveOrDestroyChildren();
   DCHECK(child_windows_.empty());
   SetFocusManager(nullptr);
@@ -481,6 +486,9 @@
   }
 
   DCHECK(wants_to_be_visible_);
+
+  ui::CATransactionCoordinator::Get().Synchronize();
+
   // If the parent (or an ancestor) is hidden, return and wait for it to become
   // visible.
   if (parent() && !parent()->IsVisibleParent())
@@ -491,6 +499,7 @@
     return;
   }
 
+  // TODO(https://crbug.com/682825): Delete this during cleanup.
   // Non-modal windows are not animated. Hence opaque non-modal windows can
   // appear with a "flash" if they are made visible before the frame from the
   // compositor arrives. To get around this, set the alpha value of the window
@@ -500,7 +509,7 @@
   // there is an active GPU process.
   // TODO(karandeepb): Investigate whether similar technique is needed for other
   // dialog types.
-  if (layer() && [window_ isOpaque] && !window_visible_ &&
+  if (false && layer() && [window_ isOpaque] && !window_visible_ &&
       !native_widget_mac_->GetWidget()->IsModal() &&
       ui::WindowResizeHelperMac::Get()->task_runner()) {
     initial_visibility_suppressed_ = true;
@@ -971,6 +980,19 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// BridgedNativeWidget, ui::CATransactionObserver
+
+bool BridgedNativeWidget::ShouldWaitInPreCommit() {
+  return window_visible_ &&
+         (!compositor_widget_ ||
+          !compositor_widget_->HasFrameOfSize(GetClientAreaSize()));
+}
+
+base::TimeDelta BridgedNativeWidget::PreCommitTimeout() {
+  return kUIPaintTimeout;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // BridgedNativeWidget, internal::InputMethodDelegate:
 
 ui::EventDispatchDetails BridgedNativeWidget::DispatchKeyEventPostIME(
@@ -1261,6 +1283,8 @@
   gfx::Size size_in_dip = GetClientAreaSize();
   gfx::Size size_in_pixel = ConvertSizeToPixel(scale_factor, size_in_dip);
 
+  ui::CATransactionCoordinator::Get().Synchronize();
+
   layer()->SetBounds(gfx::Rect(size_in_dip));
 
   if (compositor_->size() != size_in_pixel ||
@@ -1277,6 +1301,8 @@
 }
 
 void BridgedNativeWidget::MaybeWaitForFrame(const gfx::Size& size_in_dip) {
+  return;  // TODO(https://crbug.com/682825): Delete this during cleanup.
+
   if (!layer()->IsDrawn() || compositor_widget_->HasFrameOfSize(size_in_dip))
     return;
 
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index 3c1d189..85fa310 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -2136,7 +2136,8 @@
 
   // Draw placeholder text if needed.
   gfx::RenderText* render_text = GetRenderText();
-  if (text().empty() && !GetPlaceholderText().empty()) {
+  if (text().empty() && !GetPlaceholderText().empty() &&
+      (!placeholder_text_hidden_on_focus_ || !HasFocus())) {
     // Disable subpixel rendering when the background color is not opaque
     // because it draws incorrect colors around the glyphs in that case.
     // See crbug.com/786343
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index 3df902c..66a756c8 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -187,6 +187,10 @@
     placeholder_text_draw_flags_ = flags;
   }
 
+  void set_placeholder_text_hidden_on_focus(bool hidden) {
+    placeholder_text_hidden_on_focus_ = hidden;
+  }
+
   // Sets whether to indicate the textfield has invalid content.
   void SetInvalid(bool invalid);
   bool invalid() const { return invalid_; }
@@ -545,6 +549,10 @@
   // placeholder text uses the same font list as the underlying RenderText.
   base::Optional<gfx::FontList> placeholder_font_list_;
 
+  // If this is true, the placeholder text is never drawn when the Textfield is
+  // focused, regardless of whether or not it's empty.
+  bool placeholder_text_hidden_on_focus_ = false;
+
   // True when the contents are deemed unacceptable and should be indicated as
   // such.
   bool invalid_;
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 15a9ad1..83093ff 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -1898,7 +1898,13 @@
 };
 
 // Test input method focus changes affected by top window activaction.
-TEST_F(WidgetInputMethodInteractiveTest, Activation) {
+TEST_F(WidgetInputMethodInteractiveTest,
+#if defined(OS_MACOSX)
+       DISABLED_Activation
+#else
+       Activation
+#endif
+       ) {
   if (IsMus())
     return;
 
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.html b/ui/webui/resources/cr_elements/cr_input/cr_input.html
index ce54984c..e1026327 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.html
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.html
@@ -123,7 +123,8 @@
            vs .readOnly. -->
       <input id="input" disabled="[[disabled]]" autofocus="[[autofocus]]"
           value="{{value::input}}" tabindex$="[[tabIndex]]" type="[[type]]"
-          readonly$="[[readonly]]">
+          readonly$="[[readonly]]" maxlength$="[[maxlength]]"
+          pattern="[[pattern]]" required="[[required]]">
       <div id="underline"></div>
     </div>
     <div id="error">[[errorMessage]]</div>
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.js b/ui/webui/resources/cr_elements/cr_input/cr_input.js
index 0305876..3abf283 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -4,6 +4,25 @@
 
 /**
  * @fileoverview 'cr-input' is a component similar to native input.
+ *
+ * Native input attributes that are currently supported by cr-inputs are:
+ *   autofocus
+ *   disabled
+ *   maxlength
+ *   type (only 'text' and 'password')
+ *   readonly
+ *   pattern
+ *   required
+ *   value
+ *   placeholder
+ *   tabindex as 'tab-index' (e.g.: <cr-input tab-index="-1">)
+ *
+ * Additional attributes that you can use with cr-input:
+ *   label
+ *   auto-validate - triggers validation based on |pattern| and |required|,
+ *                   whenever |value| changes.
+ *   error-message - message displayed under the input when |invalid| is true.
+ *   invalid
  */
 Polymer({
   is: 'cr-input',
@@ -15,6 +34,8 @@
       reflectToAttribute: true,
     },
 
+    autoValidate: Boolean,
+
     disabled: {
       type: Boolean,
       value: false,
@@ -33,6 +54,16 @@
       reflectToAttribute: true,
     },
 
+    maxlength: {
+      type: Number,
+      reflectToAttribute: true,
+    },
+
+    pattern: {
+      type: String,
+      reflectToAttribute: true,
+    },
+
     label: {
       type: String,
       value: '',
@@ -43,7 +74,15 @@
       observer: 'placeholderChanged_',
     },
 
-    readonly: Boolean,
+    readonly: {
+      type: Boolean,
+      reflectToAttribute: true,
+    },
+
+    required: {
+      type: Boolean,
+      reflectToAttribute: true,
+    },
 
     tabIndex: String,
 
@@ -56,6 +95,7 @@
       type: String,
       value: '',
       notify: true,
+      observer: 'onValueChanged_',
     },
   },
 
@@ -98,6 +138,12 @@
       this.inputElement.focus();
   },
 
+  /** @private */
+  onValueChanged_: function() {
+    if (this.autoValidate)
+      this.invalid = !this.inputElement.checkValidity();
+  },
+
   /**
    * 'change' event fires when <input> value changes and user presses 'Enter'.
    * This function helps propagate it to host since change events don't
diff --git a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
index 2c824f5..a8272dc 100644
--- a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
+++ b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
@@ -11,9 +11,10 @@
       :host {
         -webkit-padding-start: var(--cr-toolbar-field-margin, 0);
         border-bottom: 1px solid var(--google-grey-300);
+        box-sizing: border-box;
         color: var(--google-grey-900);
         display: flex;
-        height: 55px;
+        height: 56px;
       }
 
       #overlay-content {